diff options
Diffstat (limited to 'lib')
114 files changed, 4548 insertions, 0 deletions
| diff --git a/lib/facter/facter_dot_d.rb b/lib/facter/facter_dot_d.rb new file mode 100644 index 00000000..b0584370 --- /dev/null +++ b/lib/facter/facter_dot_d.rb @@ -0,0 +1,202 @@ +# A Facter plugin that loads facts from /etc/facter/facts.d +# and /etc/puppetlabs/facter/facts.d. +# +# Facts can be in the form of JSON, YAML or Text files +# and any executable that returns key=value pairs. +# +# In the case of scripts you can also create a file that +# contains a cache TTL.  For foo.sh store the ttl as just +# a number in foo.sh.ttl +# +# The cache is stored in /tmp/facts_cache.yaml as a mode +# 600 file and will have the end result of not calling your +# fact scripts more often than is needed + +class Facter::Util::DotD +  require 'yaml' + +  def initialize(dir="/etc/facts.d", cache_file=File.join(Puppet[:libdir], "facts_dot_d.cache")) +    @dir = dir +    @cache_file = cache_file +    @cache = nil +    @types = {".txt" => :txt, ".json" => :json, ".yaml" => :yaml} +  end + +  def entries +    Dir.entries(@dir).reject { |f| f =~ /^\.|\.ttl$/ }.sort.map { |f| File.join(@dir, f) } +  rescue +    [] +  end + +  def fact_type(file) +    extension = File.extname(file) + +    type = @types[extension] || :unknown + +    type = :script if type == :unknown && File.executable?(file) + +    return type +  end + +  def txt_parser(file) +    File.readlines(file).each do |line| +      if line =~ /^([^=]+)=(.+)$/ +        var = $1; val = $2 + +        Facter.add(var) do +          setcode { val } +        end +      end +    end +  rescue Exception => e +    Facter.warn("Failed to handle #{file} as text facts: #{e.class}: #{e}") +  end + +  def json_parser(file) +    begin +      require 'json' +    rescue LoadError +      retry if require 'rubygems' +      raise +    end + +    JSON.load(File.read(file)).each_pair do |f, v| +      Facter.add(f) do +        setcode { v } +      end +    end +  rescue Exception => e +    Facter.warn("Failed to handle #{file} as json facts: #{e.class}: #{e}") +  end + +  def yaml_parser(file) +    require 'yaml' + +    YAML.load_file(file).each_pair do |f, v| +      Facter.add(f) do +        setcode { v } +      end +    end +  rescue Exception => e +    Facter.warn("Failed to handle #{file} as yaml facts: #{e.class}: #{e}") +  end + +  def script_parser(file) +    result = cache_lookup(file) +    ttl = cache_time(file) + +    unless result +      result = Facter::Util::Resolution.exec(file) + +      if ttl > 0 +        Facter.debug("Updating cache for #{file}") +        cache_store(file, result) +        cache_save! +      end +    else +      Facter.debug("Using cached data for #{file}") +    end + +    result.split("\n").each do |line| +      if line =~ /^(.+)=(.+)$/ +        var = $1; val = $2 + +        Facter.add(var) do +          setcode { val } +        end +      end +    end +  rescue Exception => e +    Facter.warn("Failed to handle #{file} as script facts: #{e.class}: #{e}") +    Facter.debug(e.backtrace.join("\n\t")) +  end + +  def cache_save! +    cache = load_cache +    File.open(@cache_file, "w", 0600) { |f| f.write(YAML.dump(cache)) } +  rescue +  end + +  def cache_store(file, data) +    load_cache + +    @cache[file] = {:data => data, :stored => Time.now.to_i} +  rescue +  end + +  def cache_lookup(file) +    cache = load_cache + +    return nil if cache.empty? + +    ttl = cache_time(file) + +    if cache[file] +      now = Time.now.to_i + +      return cache[file][:data] if ttl == -1 +      return cache[file][:data] if (now - cache[file][:stored]) <= ttl +      return nil +    else +      return nil +    end +  rescue +    return nil +  end + +  def cache_time(file) +    meta = file + ".ttl" + +    return File.read(meta).chomp.to_i +  rescue +    return 0 +  end + +  def load_cache +    unless @cache +      if File.exist?(@cache_file) +        @cache = YAML.load_file(@cache_file) +      else +        @cache = {} +      end +    end + +    return @cache +  rescue +    @cache = {} +    return @cache +  end + +  def create +    entries.each do |fact| +      type = fact_type(fact) +      parser = "#{type}_parser" + +      if respond_to?("#{type}_parser") +        Facter.debug("Parsing #{fact} using #{parser}") + +        send(parser, fact) +      end +    end +  end +end + + +mdata = Facter.version.match(/(\d+)\.(\d+)\.(\d+)/) +if mdata +  (major, minor, patch) = mdata.captures.map { |v| v.to_i } +  if major < 2 +    # Facter 1.7 introduced external facts support directly +    unless major == 1 and minor > 6 +      Facter::Util::DotD.new("/etc/facter/facts.d").create +      Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create + +      # Windows has a different configuration directory that defaults to a vendor +      # specific sub directory of the %COMMON_APPDATA% directory. +      if Dir.const_defined? 'COMMON_APPDATA' then +        windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d') +        Facter::Util::DotD.new(windows_facts_dot_d).create +      end +    end +  end +end diff --git a/lib/facter/netmask_cidr_interface.rb b/lib/facter/netmask_cidr_interface.rb new file mode 100644 index 00000000..d628d08c --- /dev/null +++ b/lib/facter/netmask_cidr_interface.rb @@ -0,0 +1,22 @@ +# adds netmask facts for each interface in cidr notation +# i.e.: +# ... +# netmask_cidr_eth2 => 24 +# netmask_cidr_lo => 8 +# netmask_cidr_tun0 => 32 +# netmask_cidr_virbr0 => 24 +# ... + +require 'facter/util/ip' + +Facter::Util::IP.get_interfaces.each do |interface| +  netmask = Facter.value("netmask_#{interface}") +  if netmask != nil +    Facter.add("netmask_cidr_" + interface ) do +      setcode do +        cidr_netmask=IPAddr.new(netmask).to_i.to_s(2).count("1") +        cidr_netmask +      end +    end +  end +end diff --git a/lib/facter/pe_version.rb b/lib/facter/pe_version.rb new file mode 100644 index 00000000..0cc0f64e --- /dev/null +++ b/lib/facter/pe_version.rb @@ -0,0 +1,53 @@ +# Fact: is_pe, pe_version, pe_major_version, pe_minor_version, pe_patch_version +# +# Purpose: Return various facts about the PE state of the system +# +# Resolution: Uses a regex match against puppetversion to determine whether the +#   machine has Puppet Enterprise installed, and what version (overall, major, +#   minor, patch) is installed. +# +# Caveats: +# +Facter.add("pe_version") do +  setcode do +    pe_ver = Facter.value("puppetversion").match(/Puppet Enterprise (\d+\.\d+\.\d+)/) +    pe_ver[1] if pe_ver +  end +end + +Facter.add("is_pe") do +  setcode do +    if Facter.value(:pe_version).to_s.empty? then +      false +    else +      true +    end +  end +end + +Facter.add("pe_major_version") do +  confine :is_pe => true +  setcode do +    if pe_version = Facter.value(:pe_version) +      pe_version.to_s.split('.')[0] +    end +  end +end + +Facter.add("pe_minor_version") do +  confine :is_pe => true +  setcode do +    if pe_version = Facter.value(:pe_version) +      pe_version.to_s.split('.')[1] +    end +  end +end + +Facter.add("pe_patch_version") do +  confine :is_pe => true +  setcode do +    if pe_version = Facter.value(:pe_version) +      pe_version.to_s.split('.')[2] +    end +  end +end diff --git a/lib/facter/puppet_vardir.rb b/lib/facter/puppet_vardir.rb new file mode 100644 index 00000000..0e6af40e --- /dev/null +++ b/lib/facter/puppet_vardir.rb @@ -0,0 +1,26 @@ +# This facter fact returns the value of the Puppet vardir setting for the node +# running puppet or puppet agent.  The intent is to enable Puppet modules to +# automatically have insight into a place where they can place variable data, +# regardless of the node's platform. +# +# The value should be directly usable in a File resource path attribute. + + +begin +  require 'facter/util/puppet_settings' +rescue LoadError => e +  # puppet apply does not add module lib directories to the $LOAD_PATH (See +  # #4248). It should (in the future) but for the time being we need to be +  # defensive which is what this rescue block is doing. +  rb_file = File.join(File.dirname(__FILE__), 'util', 'puppet_settings.rb') +  load rb_file if File.exists?(rb_file) or raise e +end + +Facter.add(:puppet_vardir) do +  setcode do +    # This will be nil if Puppet is not available. +    Facter::Util::PuppetSettings.with_puppet do +      Puppet[:vardir] +    end +  end +end diff --git a/lib/facter/root_home.rb b/lib/facter/root_home.rb new file mode 100644 index 00000000..b4f87ff2 --- /dev/null +++ b/lib/facter/root_home.rb @@ -0,0 +1,32 @@ +# A facter fact to determine the root home directory. +# This varies on PE supported platforms and may be +# reconfigured by the end user. + +module Facter::Util::RootHome +  class << self +  def get_root_home +    root_ent = Facter::Util::Resolution.exec("getent passwd root") +    # The home directory is the sixth element in the passwd entry +    # If the platform doesn't have getent, root_ent will be nil and we should +    # return it straight away. +    root_ent && root_ent.split(":")[5] +  end +  end +end + +Facter.add(:root_home) do +  setcode { Facter::Util::RootHome.get_root_home } +end + +Facter.add(:root_home) do +  confine :kernel => :darwin +  setcode do +    str = Facter::Util::Resolution.exec("dscacheutil -q user -a name root") +    hash = {} +    str.split("\n").each do |pair| +      key,value = pair.split(/:/) +      hash[key] = value +    end +    hash['dir'].strip +  end +end diff --git a/lib/facter/util/puppet_settings.rb b/lib/facter/util/puppet_settings.rb new file mode 100644 index 00000000..1ad94521 --- /dev/null +++ b/lib/facter/util/puppet_settings.rb @@ -0,0 +1,21 @@ +module Facter +  module Util +    module PuppetSettings +      # This method is intended to provide a convenient way to evaluate a +      # Facter code block only if Puppet is loaded.  This is to account for the +      # situation where the fact happens to be in the load path, but Puppet is +      # not loaded for whatever reason.  Perhaps the user is simply running +      # facter without the --puppet flag and they happen to be working in a lib +      # directory of a module. +      def self.with_puppet +        begin +          Module.const_get("Puppet") +        rescue NameError +          nil +        else +          yield +        end +      end +    end +  end +end diff --git a/lib/puppet/functions/type_of.rb b/lib/puppet/functions/type_of.rb new file mode 100644 index 00000000..02cdd4db --- /dev/null +++ b/lib/puppet/functions/type_of.rb @@ -0,0 +1,17 @@ +# Returns the type when passed a value. +# +# @example how to compare values' types +#   # compare the types of two values +#   if type_of($first_value) != type_of($second_value) { fail("first_value and second_value are different types") } +# @example how to compare against an abstract type +#   unless type_of($first_value) <= Numeric { fail("first_value must be Numeric") } +#   unless type_of{$first_value) <= Collection[1] { fail("first_value must be an Array or Hash, and contain at least one element") } +# +# See the documentation for "The Puppet Type System" for more information about types. +# See the `assert_type()` function for flexible ways to assert the type of a value. +# +Puppet::Functions.create_function(:type_of) do +  def type_of(value) +    Puppet::Pops::Types::TypeCalculator.infer_set(value) +  end +end diff --git a/lib/puppet/parser/functions/abs.rb b/lib/puppet/parser/functions/abs.rb new file mode 100644 index 00000000..11d2d7fe --- /dev/null +++ b/lib/puppet/parser/functions/abs.rb @@ -0,0 +1,36 @@ +# +# abs.rb +# + +module Puppet::Parser::Functions +  newfunction(:abs, :type => :rvalue, :doc => <<-EOS +    Returns the absolute value of a number, for example -34.56 becomes +    34.56. Takes a single integer and float value as an argument. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "abs(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    # Numbers in Puppet are often string-encoded which is troublesome ... +    if value.is_a?(String) +      if value.match(/^-?(?:\d+)(?:\.\d+){1}$/) +        value = value.to_f +      elsif value.match(/^-?\d+$/) +        value = value.to_i +      else +        raise(Puppet::ParseError, 'abs(): Requires float or ' + +          'integer to work with') +      end +    end + +    # We have numeric value to handle ... +    result = value.abs + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/any2array.rb b/lib/puppet/parser/functions/any2array.rb new file mode 100644 index 00000000..e71407e8 --- /dev/null +++ b/lib/puppet/parser/functions/any2array.rb @@ -0,0 +1,33 @@ +# +# any2array.rb +# + +module Puppet::Parser::Functions +  newfunction(:any2array, :type => :rvalue, :doc => <<-EOS +This converts any object to an array containing that object. Empty argument +lists are converted to an empty array. Arrays are left untouched. Hashes are +converted to arrays of alternating keys and values. +    EOS +  ) do |arguments| + +    if arguments.empty? +        return [] +    end + +    if arguments.length == 1 +        if arguments[0].kind_of?(Array) +            return arguments[0] +        elsif arguments[0].kind_of?(Hash) +            result = [] +            arguments[0].each do |key, value| +                result << key << value +            end +            return result +        end +    end + +    return arguments +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/base64.rb b/lib/puppet/parser/functions/base64.rb new file mode 100644 index 00000000..617ba31b --- /dev/null +++ b/lib/puppet/parser/functions/base64.rb @@ -0,0 +1,37 @@ +module Puppet::Parser::Functions + +  newfunction(:base64, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + +    Base64 encode or decode a string based on the command and the string submitted + +    Usage: + +      $encodestring = base64('encode','thestring') +      $decodestring = base64('decode','dGhlc3RyaW5n') + +    ENDHEREDOC + +    require 'base64' + +    raise Puppet::ParseError, ("base64(): Wrong number of arguments (#{args.length}; must be = 2)") unless args.length == 2 + +    actions = ['encode','decode'] + +    unless actions.include?(args[0]) +      raise Puppet::ParseError, ("base64(): the first argument must be one of 'encode' or 'decode'") +    end + +    unless args[1].is_a?(String) +      raise Puppet::ParseError, ("base64(): the second argument must be a string to base64") +    end + +    case args[0] +      when 'encode' +        result = Base64.encode64(args[1]) +      when 'decode' +        result = Base64.decode64(args[1]) +    end + +    return result +  end +end diff --git a/lib/puppet/parser/functions/basename.rb b/lib/puppet/parser/functions/basename.rb new file mode 100644 index 00000000..f7e44384 --- /dev/null +++ b/lib/puppet/parser/functions/basename.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions +  newfunction(:basename, :type => :rvalue, :doc => <<-EOS +    Strips directory (and optional suffix) from a filename +    EOS +  ) do |arguments| + +    if arguments.size < 1 then +      raise(Puppet::ParseError, "basename(): No arguments given") +    elsif arguments.size > 2 then +      raise(Puppet::ParseError, "basename(): Too many arguments given (#{arguments.size})") +    else + +      unless arguments[0].is_a?(String) +        raise(Puppet::ParseError, 'basename(): Requires string as first argument') +      end + +      if arguments.size == 1 then +        rv = File.basename(arguments[0]) +      elsif arguments.size == 2 then + +        unless arguments[1].is_a?(String) +          raise(Puppet::ParseError, 'basename(): Requires string as second argument') +        end + +        rv = File.basename(arguments[0], arguments[1]) +      end + +    end + +    return rv +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/bool2num.rb b/lib/puppet/parser/functions/bool2num.rb new file mode 100644 index 00000000..6ad6cf4e --- /dev/null +++ b/lib/puppet/parser/functions/bool2num.rb @@ -0,0 +1,26 @@ +# +# bool2num.rb +# + +module Puppet::Parser::Functions +  newfunction(:bool2num, :type => :rvalue, :doc => <<-EOS +    Converts a boolean to a number. Converts the values: +      false, f, 0, n, and no to 0 +      true, t, 1, y, and yes to 1 +    Requires a single boolean or string as an input. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "bool2num(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = function_str2bool([arguments[0]]) + +    # We have real boolean values as well ... +    result = value ? 1 : 0 + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/bool2str.rb b/lib/puppet/parser/functions/bool2str.rb new file mode 100644 index 00000000..fcd37917 --- /dev/null +++ b/lib/puppet/parser/functions/bool2str.rb @@ -0,0 +1,27 @@ +# +# bool2str.rb +# + +module Puppet::Parser::Functions +  newfunction(:bool2str, :type => :rvalue, :doc => <<-EOS +    Converts a boolean to a string. +    Requires a single boolean as an input. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "bool2str(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] +    klass = value.class + +    # We can have either true or false, and nothing else +    unless [FalseClass, TrueClass].include?(klass) +      raise(Puppet::ParseError, 'bool2str(): Requires a boolean to work with') +    end + +    return value.to_s +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/camelcase.rb b/lib/puppet/parser/functions/camelcase.rb new file mode 100644 index 00000000..d7f43f7a --- /dev/null +++ b/lib/puppet/parser/functions/camelcase.rb @@ -0,0 +1,33 @@ +# +#  camelcase.rb +# + +module Puppet::Parser::Functions +  newfunction(:camelcase, :type => :rvalue, :doc => <<-EOS +Converts the case of a string or all strings in an array to camel case. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "camelcase(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] +    klass = value.class + +    unless [Array, String].include?(klass) +      raise(Puppet::ParseError, 'camelcase(): Requires either ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      # Numbers in Puppet are often string-encoded which is troublesome ... +      result = value.collect { |i| i.is_a?(String) ? i.split('_').map{|e| e.capitalize}.join : i } +    else +      result = value.split('_').map{|e| e.capitalize}.join +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/capitalize.rb b/lib/puppet/parser/functions/capitalize.rb new file mode 100644 index 00000000..98b2d16c --- /dev/null +++ b/lib/puppet/parser/functions/capitalize.rb @@ -0,0 +1,33 @@ +# +#  capitalize.rb +# + +module Puppet::Parser::Functions +  newfunction(:capitalize, :type => :rvalue, :doc => <<-EOS +    Capitalizes the first letter of a string or array of strings. +    Requires either a single string or an array as an input. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "capitalize(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'capitalize(): Requires either ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      # Numbers in Puppet are often string-encoded which is troublesome ... +      result = value.collect { |i| i.is_a?(String) ? i.capitalize : i } +    else +      result = value.capitalize +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/chomp.rb b/lib/puppet/parser/functions/chomp.rb new file mode 100644 index 00000000..c55841e3 --- /dev/null +++ b/lib/puppet/parser/functions/chomp.rb @@ -0,0 +1,34 @@ +# +#  chomp.rb +# + +module Puppet::Parser::Functions +  newfunction(:chomp, :type => :rvalue, :doc => <<-'EOS' +    Removes the record separator from the end of a string or an array of +    strings, for example `hello\n` becomes `hello`. +    Requires a single string or array as an input. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "chomp(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'chomp(): Requires either ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      # Numbers in Puppet are often string-encoded which is troublesome ... +      result = value.collect { |i| i.is_a?(String) ? i.chomp : i } +    else +      result = value.chomp +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/chop.rb b/lib/puppet/parser/functions/chop.rb new file mode 100644 index 00000000..b24ab785 --- /dev/null +++ b/lib/puppet/parser/functions/chop.rb @@ -0,0 +1,36 @@ +# +#  chop.rb +# + +module Puppet::Parser::Functions +  newfunction(:chop, :type => :rvalue, :doc => <<-'EOS' +    Returns a new string with the last character removed. If the string ends +    with `\r\n`, both characters are removed. Applying chop to an empty +    string returns an empty string. If you wish to merely remove record +    separators then you should use the `chomp` function. +    Requires a string or array of strings as input. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "chop(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'chop(): Requires either an ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      # Numbers in Puppet are often string-encoded which is troublesome ... +      result = value.collect { |i| i.is_a?(String) ? i.chop : i } +    else +      result = value.chop +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/concat.rb b/lib/puppet/parser/functions/concat.rb new file mode 100644 index 00000000..618e62d4 --- /dev/null +++ b/lib/puppet/parser/functions/concat.rb @@ -0,0 +1,41 @@ +# +# concat.rb +# + +module Puppet::Parser::Functions +  newfunction(:concat, :type => :rvalue, :doc => <<-EOS +Appends the contents of multiple arrays into array 1. + +*Example:* + +    concat(['1','2','3'],['4','5','6'],['7','8','9']) + +Would result in: + +  ['1','2','3','4','5','6','7','8','9'] +    EOS +  ) do |arguments| + +    # Check that more than 2 arguments have been given ... +    raise(Puppet::ParseError, "concat(): Wrong number of arguments " + +      "given (#{arguments.size} for < 2)") if arguments.size < 2 + +    a = arguments[0] + +    # Check that the first parameter is an array +    unless a.is_a?(Array) +      raise(Puppet::ParseError, 'concat(): Requires array to work with') +    end + +    result = a +    arguments.shift + +    arguments.each do |x| +      result = result + Array(x) +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/count.rb b/lib/puppet/parser/functions/count.rb new file mode 100644 index 00000000..52de1b8a --- /dev/null +++ b/lib/puppet/parser/functions/count.rb @@ -0,0 +1,22 @@ +module Puppet::Parser::Functions +  newfunction(:count, :type => :rvalue, :arity => -2, :doc => <<-EOS +Takes an array as first argument and an optional second argument. +Count the number of elements in array that matches second argument. +If called with only an array it counts the number of elements that are not nil/undef. +    EOS +  ) do |args| + +    if (args.size > 2) then +      raise(ArgumentError, "count(): Wrong number of arguments "+ +        "given #{args.size} for 1 or 2.") +    end + +    collection, item = args + +    if item then +      collection.count item +    else +      collection.count { |obj| obj != nil && obj != :undef && obj != '' } +    end +  end +end diff --git a/lib/puppet/parser/functions/deep_merge.rb b/lib/puppet/parser/functions/deep_merge.rb new file mode 100644 index 00000000..6df32e9c --- /dev/null +++ b/lib/puppet/parser/functions/deep_merge.rb @@ -0,0 +1,44 @@ +module Puppet::Parser::Functions +  newfunction(:deep_merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| +    Recursively merges two or more hashes together and returns the resulting hash. + +    For example: + +        $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } +        $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } +        $merged_hash = deep_merge($hash1, $hash2) +        # The resulting hash is equivalent to: +        # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } + +    When there is a duplicate key that is a hash, they are recursively merged. +    When there is a duplicate key that is not a hash, the key in the rightmost hash will "win." + +    ENDHEREDOC + +    if args.length < 2 +      raise Puppet::ParseError, ("deep_merge(): wrong number of arguments (#{args.length}; must be at least 2)") +    end + +    deep_merge = Proc.new do |hash1,hash2| +      hash1.merge(hash2) do |key,old_value,new_value| +        if old_value.is_a?(Hash) && new_value.is_a?(Hash) +          deep_merge.call(old_value, new_value) +        else +          new_value +        end +      end +    end + +    result = Hash.new +    args.each do |arg| +      next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef +      # If the argument was not a hash, skip it. +      unless arg.is_a?(Hash) +        raise Puppet::ParseError, "deep_merge: unexpected argument type #{arg.class}, only expects hash arguments" +      end + +      result = deep_merge.call(result, arg) +    end +    return( result ) +  end +end diff --git a/lib/puppet/parser/functions/defined_with_params.rb b/lib/puppet/parser/functions/defined_with_params.rb new file mode 100644 index 00000000..d7df306c --- /dev/null +++ b/lib/puppet/parser/functions/defined_with_params.rb @@ -0,0 +1,35 @@ +# Test whether a given class or definition is defined +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:defined_with_params, +                                      :type => :rvalue, +                                      :doc => <<-'ENDOFDOC' +Takes a resource reference and an optional hash of attributes. + +Returns true if a resource with the specified attributes has already been added +to the catalog, and false otherwise. + +    user { 'dan': +      ensure => present, +    } + +    if ! defined_with_params(User[dan], {'ensure' => 'present' }) { +      user { 'dan': ensure => present, } +    } +ENDOFDOC +) do |vals| +  reference, params = vals +  raise(ArgumentError, 'Must specify a reference') unless reference +  if (! params) || params == '' +    params = {} +  end +  ret = false +  if resource = findresource(reference.to_s) +    matches = params.collect do |key, value| +      resource[key] == value +    end +    ret = params.empty? || !matches.include?(false) +  end +  Puppet.debug("Resource #{reference} was not determined to be defined") +  ret +end diff --git a/lib/puppet/parser/functions/delete.rb b/lib/puppet/parser/functions/delete.rb new file mode 100644 index 00000000..f548b444 --- /dev/null +++ b/lib/puppet/parser/functions/delete.rb @@ -0,0 +1,49 @@ +# +# delete.rb +# + +# TODO(Krzysztof Wilczynski): We need to add support for regular expression ... + +module Puppet::Parser::Functions +  newfunction(:delete, :type => :rvalue, :doc => <<-EOS +Deletes all instances of a given element from an array, substring from a +string, or key from a hash. + +*Examples:* + +    delete(['a','b','c','b'], 'b') +    Would return: ['a','c'] + +    delete({'a'=>1,'b'=>2,'c'=>3}, 'b') +    Would return: {'a'=>1,'c'=>3} + +    delete({'a'=>1,'b'=>2,'c'=>3}, ['b','c']) +    Would return: {'a'=>1} + +    delete('abracadabra', 'bra') +    Would return: 'acada' +  EOS +  ) do |arguments| + +    if (arguments.size != 2) then +      raise(Puppet::ParseError, "delete(): Wrong number of arguments "+ +                                  "given #{arguments.size} for 2.") +    end + +    collection = arguments[0].dup +    Array(arguments[1]).each do |item| +      case collection +        when Array, Hash +          collection.delete item +        when String +          collection.gsub! item, '' +        else +          raise(TypeError, "delete(): First argument must be an Array, " + +                             "String, or Hash. Given an argument of class #{collection.class}.") +      end +    end +    collection +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/delete_at.rb b/lib/puppet/parser/functions/delete_at.rb new file mode 100644 index 00000000..3eb4b537 --- /dev/null +++ b/lib/puppet/parser/functions/delete_at.rb @@ -0,0 +1,49 @@ +# +# delete_at.rb +# + +module Puppet::Parser::Functions +  newfunction(:delete_at, :type => :rvalue, :doc => <<-EOS +Deletes a determined indexed value from an array. + +*Examples:* + +    delete_at(['a','b','c'], 1) + +Would return: ['a','c'] +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "delete_at(): Wrong number of arguments " + +      "given (#{arguments.size} for 2)") if arguments.size < 2 + +    array = arguments[0] + +    unless array.is_a?(Array) +      raise(Puppet::ParseError, 'delete_at(): Requires array to work with') +    end + +    index = arguments[1] + +    if index.is_a?(String) and not index.match(/^\d+$/) +      raise(Puppet::ParseError, 'delete_at(): You must provide ' + +        'non-negative numeric index') +    end + +    result = array.clone + +    # Numbers in Puppet are often string-encoded which is troublesome ... +    index = index.to_i + +    if index > result.size - 1 # First element is at index 0 is it not? +      raise(Puppet::ParseError, 'delete_at(): Given index ' + +        'exceeds size of array given') +    end + +    result.delete_at(index) # We ignore the element that got deleted ... + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/delete_undef_values.rb b/lib/puppet/parser/functions/delete_undef_values.rb new file mode 100644 index 00000000..f94d4da8 --- /dev/null +++ b/lib/puppet/parser/functions/delete_undef_values.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions +  newfunction(:delete_undef_values, :type => :rvalue, :doc => <<-EOS +Returns a copy of input hash or array with all undefs deleted. + +*Examples:* + +    $hash = delete_undef_values({a=>'A', b=>'', c=>undef, d => false}) + +Would return: {a => 'A', b => '', d => false} + +    $array = delete_undef_values(['A','',undef,false]) + +Would return: ['A','',false] + +      EOS +    ) do |args| + +    raise(Puppet::ParseError, +          "delete_undef_values(): Wrong number of arguments given " + +          "(#{args.size})") if args.size < 1 + +    unless args[0].is_a? Array or args[0].is_a? Hash +      raise(Puppet::ParseError, +            "delete_undef_values(): expected an array or hash, got #{args[0]} type  #{args[0].class} ") +    end +    result = args[0].dup +    if result.is_a?(Hash) +      result.delete_if {|key, val| val.equal? :undef} +    elsif result.is_a?(Array) +      result.delete :undef +    end +    result +  end +end diff --git a/lib/puppet/parser/functions/delete_values.rb b/lib/puppet/parser/functions/delete_values.rb new file mode 100644 index 00000000..f6c8c0e6 --- /dev/null +++ b/lib/puppet/parser/functions/delete_values.rb @@ -0,0 +1,26 @@ +module Puppet::Parser::Functions +  newfunction(:delete_values, :type => :rvalue, :doc => <<-EOS +Deletes all instances of a given value from a hash. + +*Examples:* + +    delete_values({'a'=>'A','b'=>'B','c'=>'C','B'=>'D'}, 'B') + +Would return: {'a'=>'A','c'=>'C','B'=>'D'} + +      EOS +    ) do |arguments| + +    raise(Puppet::ParseError, +          "delete_values(): Wrong number of arguments given " + +          "(#{arguments.size} of 2)") if arguments.size != 2 + +    hash, item = arguments + +    if not hash.is_a?(Hash) +      raise(TypeError, "delete_values(): First argument must be a Hash. " + \ +                       "Given an argument of class #{hash.class}.") +    end +    hash.dup.delete_if { |key, val| item == val } +  end +end diff --git a/lib/puppet/parser/functions/difference.rb b/lib/puppet/parser/functions/difference.rb new file mode 100644 index 00000000..cd258f75 --- /dev/null +++ b/lib/puppet/parser/functions/difference.rb @@ -0,0 +1,36 @@ +# +# difference.rb +# + +module Puppet::Parser::Functions +  newfunction(:difference, :type => :rvalue, :doc => <<-EOS +This function returns the difference between two arrays. +The returned array is a copy of the original array, removing any items that +also appear in the second array. + +*Examples:* + +    difference(["a","b","c"],["b","c","d"]) + +Would return: ["a"] +    EOS +  ) do |arguments| + +    # Two arguments are required +    raise(Puppet::ParseError, "difference(): Wrong number of arguments " + +      "given (#{arguments.size} for 2)") if arguments.size != 2 + +    first = arguments[0] +    second = arguments[1] + +    unless first.is_a?(Array) && second.is_a?(Array) +      raise(Puppet::ParseError, 'difference(): Requires 2 arrays') +    end + +    result = first - second + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/dirname.rb b/lib/puppet/parser/functions/dirname.rb new file mode 100644 index 00000000..ea8cc1e0 --- /dev/null +++ b/lib/puppet/parser/functions/dirname.rb @@ -0,0 +1,15 @@ +module Puppet::Parser::Functions +  newfunction(:dirname, :type => :rvalue, :doc => <<-EOS +    Returns the dirname of a path. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "dirname(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    path = arguments[0] +    return File.dirname(path) +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/downcase.rb b/lib/puppet/parser/functions/downcase.rb new file mode 100644 index 00000000..040b84f5 --- /dev/null +++ b/lib/puppet/parser/functions/downcase.rb @@ -0,0 +1,32 @@ +# +#  downcase.rb +# + +module Puppet::Parser::Functions +  newfunction(:downcase, :type => :rvalue, :doc => <<-EOS +Converts the case of a string or all strings in an array to lower case. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "downcase(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'downcase(): Requires either ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      # Numbers in Puppet are often string-encoded which is troublesome ... +      result = value.collect { |i| i.is_a?(String) ? i.downcase : i } +    else +      result = value.downcase +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/empty.rb b/lib/puppet/parser/functions/empty.rb new file mode 100644 index 00000000..cca620fa --- /dev/null +++ b/lib/puppet/parser/functions/empty.rb @@ -0,0 +1,27 @@ +# +# empty.rb +# + +module Puppet::Parser::Functions +  newfunction(:empty, :type => :rvalue, :doc => <<-EOS +Returns true if the variable is empty. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "empty(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(Hash) || value.is_a?(String) +      raise(Puppet::ParseError, 'empty(): Requires either ' + +        'array, hash or string to work with') +    end + +    result = value.empty? + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/ensure_packages.rb b/lib/puppet/parser/functions/ensure_packages.rb new file mode 100644 index 00000000..f1da4aaa --- /dev/null +++ b/lib/puppet/parser/functions/ensure_packages.rb @@ -0,0 +1,35 @@ +# +# ensure_packages.rb +# + +module Puppet::Parser::Functions +  newfunction(:ensure_packages, :type => :statement, :doc => <<-EOS +Takes a list of packages and only installs them if they don't already exist. +It optionally takes a hash as a second parameter that will be passed as the +third argument to the ensure_resource() function. +    EOS +  ) do |arguments| + +    if arguments.size > 2 or arguments.size == 0 +      raise(Puppet::ParseError, "ensure_packages(): Wrong number of arguments " + +        "given (#{arguments.size} for 1 or 2)") +    elsif arguments.size == 2 and !arguments[1].is_a?(Hash)  +      raise(Puppet::ParseError, 'ensure_packages(): Requires second argument to be a Hash') +    end + +    packages = Array(arguments[0]) + +    if arguments[1] +      defaults = { 'ensure' => 'present' }.merge(arguments[1]) +    else +      defaults = { 'ensure' => 'present' } +    end + +    Puppet::Parser::Functions.function(:ensure_resource) +    packages.each { |package_name| +      function_ensure_resource(['package', package_name, defaults ]) +    } +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/ensure_resource.rb b/lib/puppet/parser/functions/ensure_resource.rb new file mode 100644 index 00000000..1ba6a447 --- /dev/null +++ b/lib/puppet/parser/functions/ensure_resource.rb @@ -0,0 +1,46 @@ +# Test whether a given class or definition is defined +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:ensure_resource, +                                      :type => :statement, +                                      :doc => <<-'ENDOFDOC' +Takes a resource type, title, and a list of attributes that describe a +resource. + +    user { 'dan': +      ensure => present, +    } + +This example only creates the resource if it does not already exist: + +    ensure_resource('user', 'dan', {'ensure' => 'present' }) + +If the resource already exists but does not match the specified parameters, +this function will attempt to recreate the resource leading to a duplicate +resource definition error. + +An array of resources can also be passed in and each will be created with +the type and parameters specified if it doesn't already exist. + +    ensure_resource('user', ['dan','alex'], {'ensure' => 'present'}) + +ENDOFDOC +) do |vals| +  type, title, params = vals +  raise(ArgumentError, 'Must specify a type') unless type +  raise(ArgumentError, 'Must specify a title') unless title +  params ||= {} + +  items = [title].flatten + +  items.each do |item| +    Puppet::Parser::Functions.function(:defined_with_params) +    if function_defined_with_params(["#{type}[#{item}]", params]) +      Puppet.debug("Resource #{type}[#{item}] with params #{params} not created because it already exists") +    else +      Puppet.debug("Create new resource #{type}[#{item}] with params #{params}") +      Puppet::Parser::Functions.function(:create_resources) +      function_create_resources([type.capitalize, { item => params }]) +    end +  end +end diff --git a/lib/puppet/parser/functions/flatten.rb b/lib/puppet/parser/functions/flatten.rb new file mode 100644 index 00000000..a1ed1832 --- /dev/null +++ b/lib/puppet/parser/functions/flatten.rb @@ -0,0 +1,33 @@ +# +# flatten.rb +# + +module Puppet::Parser::Functions +  newfunction(:flatten, :type => :rvalue, :doc => <<-EOS +This function flattens any deeply nested arrays and returns a single flat array +as a result. + +*Examples:* + +    flatten(['a', ['b', ['c']]]) + +Would return: ['a','b','c'] +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "flatten(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size != 1 + +    array = arguments[0] + +    unless array.is_a?(Array) +      raise(Puppet::ParseError, 'flatten(): Requires array to work with') +    end + +    result = array.flatten + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/floor.rb b/lib/puppet/parser/functions/floor.rb new file mode 100644 index 00000000..9a6f014d --- /dev/null +++ b/lib/puppet/parser/functions/floor.rb @@ -0,0 +1,25 @@ +module Puppet::Parser::Functions +  newfunction(:floor, :type => :rvalue, :doc => <<-EOS +    Returns the largest integer less or equal to the argument. +    Takes a single numeric value as an argument. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "floor(): Wrong number of arguments " + +          "given (#{arguments.size} for 1)") if arguments.size != 1 + +    begin +      arg = Float(arguments[0]) +    rescue TypeError, ArgumentError => e +      raise(Puppet::ParseError, "floor(): Wrong argument type " + +            "given (#{arguments[0]} for Numeric)") +    end + +    raise(Puppet::ParseError, "floor(): Wrong argument type " + +          "given (#{arg.class} for Numeric)") if arg.is_a?(Numeric) == false + +    arg.floor +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/fqdn_rotate.rb b/lib/puppet/parser/functions/fqdn_rotate.rb new file mode 100644 index 00000000..7f4d37d0 --- /dev/null +++ b/lib/puppet/parser/functions/fqdn_rotate.rb @@ -0,0 +1,45 @@ +# +# fqdn_rotate.rb +# + +module Puppet::Parser::Functions +  newfunction(:fqdn_rotate, :type => :rvalue, :doc => <<-EOS +Rotates an array a random number of times based on a nodes fqdn. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "fqdn_rotate(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] +    require 'digest/md5' + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'fqdn_rotate(): Requires either ' + +        'array or string to work with') +    end + +    result = value.clone + +    string = value.is_a?(String) ? true : false + +    # Check whether it makes sense to rotate ... +    return result if result.size <= 1 + +    # We turn any string value into an array to be able to rotate ... +    result = string ? result.split('') : result + +    elements = result.size + +    srand(Digest::MD5.hexdigest([lookupvar('::fqdn'),arguments].join(':')).hex) +    rand(elements).times { +       result.push result.shift +    } + +    result = string ? result.join : result + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/get_module_path.rb b/lib/puppet/parser/functions/get_module_path.rb new file mode 100644 index 00000000..1421b91f --- /dev/null +++ b/lib/puppet/parser/functions/get_module_path.rb @@ -0,0 +1,17 @@ +module Puppet::Parser::Functions +  newfunction(:get_module_path, :type =>:rvalue, :doc => <<-EOT +    Returns the absolute path of the specified module for the current +    environment. + +    Example: +      $module_path = get_module_path('stdlib') +  EOT +  ) do |args| +    raise(Puppet::ParseError, "get_module_path(): Wrong number of arguments, expects one") unless args.size == 1 +    if module_path = Puppet::Module.find(args[0], compiler.environment.to_s) +      module_path.path +    else +      raise(Puppet::ParseError, "Could not find module #{args[0]} in environment #{compiler.environment}") +    end +  end +end diff --git a/lib/puppet/parser/functions/getparam.rb b/lib/puppet/parser/functions/getparam.rb new file mode 100644 index 00000000..6d510069 --- /dev/null +++ b/lib/puppet/parser/functions/getparam.rb @@ -0,0 +1,35 @@ +# Test whether a given class or definition is defined +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:getparam, +                                      :type => :rvalue, +                                      :doc => <<-'ENDOFDOC' +Takes a resource reference and name of the parameter and +returns value of resource's parameter. + +*Examples:* + +    define example_resource($param) { +    } + +    example_resource { "example_resource_instance": +        param => "param_value" +    } + +    getparam(Example_resource["example_resource_instance"], "param") + +Would return: param_value +ENDOFDOC +) do |vals| +  reference, param = vals +  raise(ArgumentError, 'Must specify a reference') unless reference +  raise(ArgumentError, 'Must specify name of a parameter') unless param and param.instance_of? String + +  return '' if param.empty? + +  if resource = findresource(reference.to_s) +    return resource[param] if resource[param] +  end + +  return '' +end diff --git a/lib/puppet/parser/functions/getvar.rb b/lib/puppet/parser/functions/getvar.rb new file mode 100644 index 00000000..fb336b6a --- /dev/null +++ b/lib/puppet/parser/functions/getvar.rb @@ -0,0 +1,29 @@ +module Puppet::Parser::Functions + +  newfunction(:getvar, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| +    Lookup a variable in a remote namespace. + +    For example: + +        $foo = getvar('site::data::foo') +        # Equivalent to $foo = $site::data::foo + +    This is useful if the namespace itself is stored in a string: + +        $datalocation = 'site::data' +        $bar = getvar("${datalocation}::bar") +        # Equivalent to $bar = $site::data::bar +    ENDHEREDOC + +    unless args.length == 1 +      raise Puppet::ParseError, ("getvar(): wrong number of arguments (#{args.length}; must be 1)") +    end + +    begin +      self.lookupvar("#{args[0]}") +    rescue Puppet::ParseError # Eat the exception if strict_variables = true is set +    end + +  end + +end diff --git a/lib/puppet/parser/functions/grep.rb b/lib/puppet/parser/functions/grep.rb new file mode 100644 index 00000000..ceba9ecc --- /dev/null +++ b/lib/puppet/parser/functions/grep.rb @@ -0,0 +1,33 @@ +# +# grep.rb +# + +module Puppet::Parser::Functions +  newfunction(:grep, :type => :rvalue, :doc => <<-EOS +This function searches through an array and returns any elements that match +the provided regular expression. + +*Examples:* + +    grep(['aaa','bbb','ccc','aaaddd'], 'aaa') + +Would return: + +    ['aaa','aaaddd'] +    EOS +  ) do |arguments| + +    if (arguments.size != 2) then +      raise(Puppet::ParseError, "grep(): Wrong number of arguments "+ +        "given #{arguments.size} for 2") +    end + +    a = arguments[0] +    pattern = Regexp.new(arguments[1]) + +    a.grep(pattern) + +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/has_interface_with.rb b/lib/puppet/parser/functions/has_interface_with.rb new file mode 100644 index 00000000..36915246 --- /dev/null +++ b/lib/puppet/parser/functions/has_interface_with.rb @@ -0,0 +1,68 @@ +# +# has_interface_with +# + +module Puppet::Parser::Functions +  newfunction(:has_interface_with, :type => :rvalue, :doc => <<-EOS +Returns boolean based on kind and value: +  * macaddress +  * netmask +  * ipaddress +  * network + +has_interface_with("macaddress", "x:x:x:x:x:x") +has_interface_with("ipaddress", "127.0.0.1")    => true +etc. + +If no "kind" is given, then the presence of the interface is checked: +has_interface_with("lo")                        => true +    EOS +  ) do |args| + +    raise(Puppet::ParseError, "has_interface_with(): Wrong number of arguments " + +          "given (#{args.size} for 1 or 2)") if args.size < 1 or args.size > 2 + +    interfaces = lookupvar('interfaces') + +    # If we do not have any interfaces, then there are no requested attributes +    return false if (interfaces == :undefined || interfaces.nil?) + +    interfaces = interfaces.split(',') + +    if args.size == 1 +      return interfaces.member?(args[0]) +    end + +    kind, value = args + +    # Bug with 3.7.1 - 3.7.3  when using future parser throws :undefined_variable +    # https://tickets.puppetlabs.com/browse/PUP-3597 +    factval = nil +    catch :undefined_variable do +      factval = lookupvar(kind) +    end +    if factval == value +      return true +    end + +    result = false +    interfaces.each do |iface| +      iface.downcase! +      factval = nil +      begin +        # Bug with 3.7.1 - 3.7.3 when using future parser throws :undefined_variable +        # https://tickets.puppetlabs.com/browse/PUP-3597 +        catch :undefined_variable do +          factval = lookupvar("#{kind}_#{iface}") +        end +      rescue Puppet::ParseError # Eat the exception if strict_variables = true is set +      end +      if value == factval +        result = true +        break +      end +    end + +    result +  end +end diff --git a/lib/puppet/parser/functions/has_ip_address.rb b/lib/puppet/parser/functions/has_ip_address.rb new file mode 100644 index 00000000..842c8ec6 --- /dev/null +++ b/lib/puppet/parser/functions/has_ip_address.rb @@ -0,0 +1,25 @@ +# +# has_ip_address +# + +module Puppet::Parser::Functions +  newfunction(:has_ip_address, :type => :rvalue, :doc => <<-EOS +Returns true if the client has the requested IP address on some interface. + +This function iterates through the 'interfaces' fact and checks the +'ipaddress_IFACE' facts, performing a simple string comparison. +    EOS +  ) do |args| + +    raise(Puppet::ParseError, "has_ip_address(): Wrong number of arguments " + +          "given (#{args.size} for 1)") if args.size != 1 + +    Puppet::Parser::Functions.autoloader.load(:has_interface_with) \ +      unless Puppet::Parser::Functions.autoloader.loaded?(:has_interface_with) + +    function_has_interface_with(['ipaddress', args[0]]) + +  end +end + +# vim:sts=2 sw=2 diff --git a/lib/puppet/parser/functions/has_ip_network.rb b/lib/puppet/parser/functions/has_ip_network.rb new file mode 100644 index 00000000..9ccf9024 --- /dev/null +++ b/lib/puppet/parser/functions/has_ip_network.rb @@ -0,0 +1,25 @@ +# +# has_ip_network +# + +module Puppet::Parser::Functions +  newfunction(:has_ip_network, :type => :rvalue, :doc => <<-EOS +Returns true if the client has an IP address within the requested network. + +This function iterates through the 'interfaces' fact and checks the +'network_IFACE' facts, performing a simple string comparision. +    EOS +  ) do |args| + +    raise(Puppet::ParseError, "has_ip_network(): Wrong number of arguments " + +          "given (#{args.size} for 1)") if args.size != 1 + +    Puppet::Parser::Functions.autoloader.load(:has_interface_with) \ +      unless Puppet::Parser::Functions.autoloader.loaded?(:has_interface_with) + +    function_has_interface_with(['network', args[0]]) + +  end +end + +# vim:sts=2 sw=2 diff --git a/lib/puppet/parser/functions/has_key.rb b/lib/puppet/parser/functions/has_key.rb new file mode 100644 index 00000000..4657cc29 --- /dev/null +++ b/lib/puppet/parser/functions/has_key.rb @@ -0,0 +1,28 @@ +module Puppet::Parser::Functions + +  newfunction(:has_key, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| +    Determine if a hash has a certain key value. + +    Example: + +        $my_hash = {'key_one' => 'value_one'} +        if has_key($my_hash, 'key_two') { +          notice('we will not reach here') +        } +        if has_key($my_hash, 'key_one') { +          notice('this will be printed') +        } + +    ENDHEREDOC + +    unless args.length == 2 +      raise Puppet::ParseError, ("has_key(): wrong number of arguments (#{args.length}; must be 2)") +    end +    unless args[0].is_a?(Hash) +      raise Puppet::ParseError, "has_key(): expects the first argument to be a hash, got #{args[0].inspect} which is of type #{args[0].class}" +    end +    args[0].has_key?(args[1]) + +  end + +end diff --git a/lib/puppet/parser/functions/hash.rb b/lib/puppet/parser/functions/hash.rb new file mode 100644 index 00000000..8cc4823b --- /dev/null +++ b/lib/puppet/parser/functions/hash.rb @@ -0,0 +1,41 @@ +# +# hash.rb +# + +module Puppet::Parser::Functions +  newfunction(:hash, :type => :rvalue, :doc => <<-EOS +This function converts an array into a hash. + +*Examples:* + +    hash(['a',1,'b',2,'c',3]) + +Would return: {'a'=>1,'b'=>2,'c'=>3} +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "hash(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    array = arguments[0] + +    unless array.is_a?(Array) +      raise(Puppet::ParseError, 'hash(): Requires array to work with') +    end + +    result = {} + +    begin +      # This is to make it compatible with older version of Ruby ... +      array  = array.flatten +      result = Hash[*array] +    rescue Exception +      raise(Puppet::ParseError, 'hash(): Unable to compute ' + +        'hash from array given') +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/intersection.rb b/lib/puppet/parser/functions/intersection.rb new file mode 100644 index 00000000..48f02e9d --- /dev/null +++ b/lib/puppet/parser/functions/intersection.rb @@ -0,0 +1,34 @@ +# +# intersection.rb +# + +module Puppet::Parser::Functions +  newfunction(:intersection, :type => :rvalue, :doc => <<-EOS +This function returns an array an intersection of two. + +*Examples:* + +    intersection(["a","b","c"],["b","c","d"]) + +Would return: ["b","c"] +    EOS +  ) do |arguments| + +    # Two arguments are required +    raise(Puppet::ParseError, "intersection(): Wrong number of arguments " + +      "given (#{arguments.size} for 2)") if arguments.size != 2 + +    first = arguments[0] +    second = arguments[1] + +    unless first.is_a?(Array) && second.is_a?(Array) +      raise(Puppet::ParseError, 'intersection(): Requires 2 arrays') +    end + +    result = first & second + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_array.rb b/lib/puppet/parser/functions/is_array.rb new file mode 100644 index 00000000..b39e184a --- /dev/null +++ b/lib/puppet/parser/functions/is_array.rb @@ -0,0 +1,22 @@ +# +# is_array.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_array, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is an array. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "is_array(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    type = arguments[0] + +    result = type.is_a?(Array) + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_bool.rb b/lib/puppet/parser/functions/is_bool.rb new file mode 100644 index 00000000..8bbdbc8a --- /dev/null +++ b/lib/puppet/parser/functions/is_bool.rb @@ -0,0 +1,22 @@ +# +# is_bool.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_bool, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a boolean. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "is_bool(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size != 1 + +    type = arguments[0] + +    result = type.is_a?(TrueClass) || type.is_a?(FalseClass) + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_domain_name.rb b/lib/puppet/parser/functions/is_domain_name.rb new file mode 100644 index 00000000..b3fee965 --- /dev/null +++ b/lib/puppet/parser/functions/is_domain_name.rb @@ -0,0 +1,50 @@ +# +# is_domain_name.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_domain_name, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a syntactically correct domain name. +    EOS +  ) do |arguments| + +    if (arguments.size != 1) then +      raise(Puppet::ParseError, "is_domain_name(): Wrong number of arguments "+ +        "given #{arguments.size} for 1") +    end + +    domain = arguments[0] + +    # Limits (rfc1035, 3.1) +    domain_max_length=255 +    label_min_length=1 +    label_max_length=63 + +    # Only allow string types +    return false unless domain.is_a?(String) + +    # Allow ".", it is the top level domain +    return true if domain == '.' + +    # Remove the final dot, if present. +    domain.chomp!('.') + +    # Check the whole domain +    return false if domain.empty? +    return false if domain.length > domain_max_length + +    # Check each label in the domain +    labels = domain.split('.') +    vlabels = labels.each do |label| +      break if label.length < label_min_length +      break if label.length > label_max_length +      break if label[-1..-1] == '-' +      break if label[0..0] == '-' +      break unless /^[a-z\d-]+$/i.match(label) +    end +    return vlabels == labels + +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_float.rb b/lib/puppet/parser/functions/is_float.rb new file mode 100644 index 00000000..a2da9438 --- /dev/null +++ b/lib/puppet/parser/functions/is_float.rb @@ -0,0 +1,30 @@ +# +# is_float.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_float, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a float. +    EOS +  ) do |arguments| + +    if (arguments.size != 1) then +      raise(Puppet::ParseError, "is_float(): Wrong number of arguments "+ +        "given #{arguments.size} for 1") +    end + +    value = arguments[0] + +    # Only allow Numeric or String types +    return false unless value.is_a?(Numeric) or value.is_a?(String) + +    if value != value.to_f.to_s and !value.is_a? Float then +      return false +    else +      return true +    end + +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_function_available.rb b/lib/puppet/parser/functions/is_function_available.rb new file mode 100644 index 00000000..6da82c8c --- /dev/null +++ b/lib/puppet/parser/functions/is_function_available.rb @@ -0,0 +1,26 @@ +# +# is_function_available.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_function_available, :type => :rvalue, :doc => <<-EOS +This function accepts a string as an argument, determines whether the +Puppet runtime has access to a function by that name.  It returns a +true if the function exists, false if not. +    EOS +  ) do |arguments| + +    if (arguments.size != 1) then +      raise(Puppet::ParseError, "is_function_available?(): Wrong number of arguments "+ +        "given #{arguments.size} for 1") +    end + +    # Only allow String types +    return false unless arguments[0].is_a?(String) + +    function = Puppet::Parser::Functions.function(arguments[0].to_sym) +    function.is_a?(String) and not function.empty? +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_hash.rb b/lib/puppet/parser/functions/is_hash.rb new file mode 100644 index 00000000..ad907f08 --- /dev/null +++ b/lib/puppet/parser/functions/is_hash.rb @@ -0,0 +1,22 @@ +# +# is_hash.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_hash, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a hash. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "is_hash(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size != 1 + +    type = arguments[0] + +    result = type.is_a?(Hash) + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_integer.rb b/lib/puppet/parser/functions/is_integer.rb new file mode 100644 index 00000000..c03d28df --- /dev/null +++ b/lib/puppet/parser/functions/is_integer.rb @@ -0,0 +1,45 @@ +# +# is_integer.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_integer, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is an Integer or +a decimal (base 10) integer in String form. The string may +start with a '-' (minus). A value of '0' is allowed, but a leading '0' digit may not +be followed by other digits as this indicates that the value is octal (base 8). + +If given any other argument `false` is returned. +    EOS +  ) do |arguments| + +    if (arguments.size != 1) then +      raise(Puppet::ParseError, "is_integer(): Wrong number of arguments "+ +        "given #{arguments.size} for 1") +    end + +    value = arguments[0] + +    # Regex is taken from the lexer of puppet +    # puppet/pops/parser/lexer.rb but modified to match also +    # negative values and disallow numbers prefixed with multiple +    # 0's +    # +    # TODO these parameter should be a constant but I'm not sure +    # if there is no risk to declare it inside of the module +    # Puppet::Parser::Functions + +    # Integer numbers like +    # -1234568981273 +    # 47291 +    numeric = %r{^-?(?:(?:[1-9]\d*)|0)$} + +    if value.is_a? Integer or (value.is_a? String and value.match numeric) +      return true +    else +      return false +    end +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_ip_address.rb b/lib/puppet/parser/functions/is_ip_address.rb new file mode 100644 index 00000000..a90adabe --- /dev/null +++ b/lib/puppet/parser/functions/is_ip_address.rb @@ -0,0 +1,32 @@ +# +# is_ip_address.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_ip_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid IP address. +    EOS +  ) do |arguments| + +    require 'ipaddr' + +    if (arguments.size != 1) then +      raise(Puppet::ParseError, "is_ip_address(): Wrong number of arguments "+ +        "given #{arguments.size} for 1") +    end + +    begin +      ip = IPAddr.new(arguments[0]) +    rescue ArgumentError +      return false +    end + +    if ip.ipv4? or ip.ipv6? then +      return true +    else +      return false +    end +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_mac_address.rb b/lib/puppet/parser/functions/is_mac_address.rb new file mode 100644 index 00000000..1b3088a2 --- /dev/null +++ b/lib/puppet/parser/functions/is_mac_address.rb @@ -0,0 +1,27 @@ +# +# is_mac_address.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_mac_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid mac address. +    EOS +  ) do |arguments| + +    if (arguments.size != 1) then +      raise(Puppet::ParseError, "is_mac_address(): Wrong number of arguments "+ +        "given #{arguments.size} for 1") +    end + +    mac = arguments[0] + +    if /^[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}$/.match(mac) then +      return true +    else +      return false +    end + +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_numeric.rb b/lib/puppet/parser/functions/is_numeric.rb new file mode 100644 index 00000000..e7e1d2a7 --- /dev/null +++ b/lib/puppet/parser/functions/is_numeric.rb @@ -0,0 +1,75 @@ +# +# is_numeric.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_numeric, :type => :rvalue, :doc => <<-EOS +Returns true if the given argument is a Numeric (Integer or Float), +or a String containing either a valid integer in decimal base 10 form, or +a valid floating point string representation. + +The function recognizes only decimal (base 10) integers and float but not +integers in hex (base 16) or octal (base 8) form. + +The string representation may start with a '-' (minus). If a decimal '.' is used, +it must be followed by at least one digit. + +Valid examples: + +  77435 +  10e-12 +  -8475 +  0.2343 +  -23.561e3 +    EOS +  ) do |arguments| + +    if (arguments.size != 1) then +      raise(Puppet::ParseError, "is_numeric(): Wrong number of arguments "+ +        "given #{arguments.size} for 1") +    end + +    value = arguments[0] + +    # Regex is taken from the lexer of puppet +    # puppet/pops/parser/lexer.rb but modified to match also +    # negative values and disallow invalid octal numbers or +    # numbers prefixed with multiple 0's (except in hex numbers) +    # +    # TODO these parameters should be constants but I'm not sure +    # if there is no risk to declare them inside of the module +    # Puppet::Parser::Functions + +    # TODO decide if this should be used +    # HEX numbers like +    # 0xaa230F +    # 0X1234009C +    # 0x0012 +    # -12FcD +    #numeric_hex = %r{^-?0[xX][0-9A-Fa-f]+$} + +    # TODO decide if this should be used +    # OCTAL numbers like +    # 01234567 +    # -045372 +    #numeric_oct = %r{^-?0[1-7][0-7]*$} + +    # Integer/Float numbers like +    # -0.1234568981273 +    # 47291 +    # 42.12345e-12 +    numeric = %r{^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)?(?:[eE]-?\d+)?$} + +    if value.is_a? Numeric or (value.is_a? String and ( +      value.match(numeric) #or +    #  value.match(numeric_hex) or +    #  value.match(numeric_oct) +    )) +      return true +    else +      return false +    end +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_string.rb b/lib/puppet/parser/functions/is_string.rb new file mode 100644 index 00000000..f5bef045 --- /dev/null +++ b/lib/puppet/parser/functions/is_string.rb @@ -0,0 +1,26 @@ +# +# is_string.rb +# + +module Puppet::Parser::Functions +  newfunction(:is_string, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a string. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "is_string(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    type = arguments[0] + +    result = type.is_a?(String) + +    if result and (type == type.to_f.to_s or type == type.to_i.to_s) then +      return false +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/join.rb b/lib/puppet/parser/functions/join.rb new file mode 100644 index 00000000..6c0a6ba0 --- /dev/null +++ b/lib/puppet/parser/functions/join.rb @@ -0,0 +1,41 @@ +# +# join.rb +# + +module Puppet::Parser::Functions +  newfunction(:join, :type => :rvalue, :doc => <<-EOS +This function joins an array into a string using a separator. + +*Examples:* + +    join(['a','b','c'], ",") + +Would result in: "a,b,c" +    EOS +  ) do |arguments| + +    # Technically we support two arguments but only first is mandatory ... +    raise(Puppet::ParseError, "join(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    array = arguments[0] + +    unless array.is_a?(Array) +      raise(Puppet::ParseError, 'join(): Requires array to work with') +    end + +    suffix = arguments[1] if arguments[1] + +    if suffix +      unless suffix.is_a?(String) +        raise(Puppet::ParseError, 'join(): Requires string to work with') +      end +    end + +    result = suffix ? array.join(suffix) : array.join + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/join_keys_to_values.rb b/lib/puppet/parser/functions/join_keys_to_values.rb new file mode 100644 index 00000000..e9924fe2 --- /dev/null +++ b/lib/puppet/parser/functions/join_keys_to_values.rb @@ -0,0 +1,47 @@ +# +# join.rb +# + +module Puppet::Parser::Functions +  newfunction(:join_keys_to_values, :type => :rvalue, :doc => <<-EOS +This function joins each key of a hash to that key's corresponding value with a +separator. Keys and values are cast to strings. The return value is an array in +which each element is one joined key/value pair. + +*Examples:* + +    join_keys_to_values({'a'=>1,'b'=>2}, " is ") + +Would result in: ["a is 1","b is 2"] +    EOS +  ) do |arguments| + +    # Validate the number of arguments. +    if arguments.size != 2 +      raise(Puppet::ParseError, "join_keys_to_values(): Takes exactly two " + +            "arguments, but #{arguments.size} given.") +    end + +    # Validate the first argument. +    hash = arguments[0] +    if not hash.is_a?(Hash) +      raise(TypeError, "join_keys_to_values(): The first argument must be a " + +            "hash, but a #{hash.class} was given.") +    end + +    # Validate the second argument. +    separator = arguments[1] +    if not separator.is_a?(String) +      raise(TypeError, "join_keys_to_values(): The second argument must be a " + +            "string, but a #{separator.class} was given.") +    end + +    # Join the keys to their values. +    hash.map do |k,v| +      String(k) + separator + String(v) +    end + +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/keys.rb b/lib/puppet/parser/functions/keys.rb new file mode 100644 index 00000000..f0d13b64 --- /dev/null +++ b/lib/puppet/parser/functions/keys.rb @@ -0,0 +1,26 @@ +# +# keys.rb +# + +module Puppet::Parser::Functions +  newfunction(:keys, :type => :rvalue, :doc => <<-EOS +Returns the keys of a hash as an array. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "keys(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    hash = arguments[0] + +    unless hash.is_a?(Hash) +      raise(Puppet::ParseError, 'keys(): Requires hash to work with') +    end + +    result = hash.keys + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/loadyaml.rb b/lib/puppet/parser/functions/loadyaml.rb new file mode 100644 index 00000000..10c40050 --- /dev/null +++ b/lib/puppet/parser/functions/loadyaml.rb @@ -0,0 +1,20 @@ +module Puppet::Parser::Functions + +  newfunction(:loadyaml, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| +    Load a YAML file containing an array, string, or hash, and return the data +    in the corresponding native data type. + +    For example: + +        $myhash = loadyaml('/etc/puppet/data/myhash.yaml') +    ENDHEREDOC + +    unless args.length == 1 +      raise Puppet::ParseError, ("loadyaml(): wrong number of arguments (#{args.length}; must be 1)") +    end + +    YAML.load_file(args[0]) + +  end + +end diff --git a/lib/puppet/parser/functions/lstrip.rb b/lib/puppet/parser/functions/lstrip.rb new file mode 100644 index 00000000..624e4c84 --- /dev/null +++ b/lib/puppet/parser/functions/lstrip.rb @@ -0,0 +1,32 @@ +# +#  lstrip.rb +# + +module Puppet::Parser::Functions +  newfunction(:lstrip, :type => :rvalue, :doc => <<-EOS +Strips leading spaces to the left of a string. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "lstrip(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'lstrip(): Requires either ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      # Numbers in Puppet are often string-encoded which is troublesome ... +      result = value.collect { |i| i.is_a?(String) ? i.lstrip : i } +    else +      result = value.lstrip +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/max.rb b/lib/puppet/parser/functions/max.rb new file mode 100644 index 00000000..60fb94ac --- /dev/null +++ b/lib/puppet/parser/functions/max.rb @@ -0,0 +1,21 @@ +module Puppet::Parser::Functions +  newfunction(:max, :type => :rvalue, :doc => <<-EOS +    Returns the highest value of all arguments. +    Requires at least one argument. +    EOS +  ) do |args| + +    raise(Puppet::ParseError, "max(): Wrong number of arguments " + +          "need at least one") if args.size == 0 + +    # Sometimes we get numbers as numerics and sometimes as strings. +    # We try to compare them as numbers when possible +    return args.max do |a,b| +      if a.to_s =~ /\A-?\d+(.\d+)?\z/ and b.to_s =~ /\A-?\d+(.\d+)?\z/ then +        a.to_f <=> b.to_f +      else +        a.to_s <=> b.to_s +      end +    end +  end +end diff --git a/lib/puppet/parser/functions/member.rb b/lib/puppet/parser/functions/member.rb new file mode 100644 index 00000000..88609ce5 --- /dev/null +++ b/lib/puppet/parser/functions/member.rb @@ -0,0 +1,62 @@ +# +# member.rb +# + +# TODO(Krzysztof Wilczynski): We need to add support for regular expression ... +# TODO(Krzysztof Wilczynski): Support for strings and hashes too ... + +module Puppet::Parser::Functions +  newfunction(:member, :type => :rvalue, :doc => <<-EOS +This function determines if a variable is a member of an array. +The variable can be a string, fixnum, or array. + +*Examples:* + +    member(['a','b'], 'b') + +Would return: true + +    member(['a', 'b', 'c'], ['a', 'b']) + +would return: true + +    member(['a','b'], 'c') + +Would return: false + +    member(['a', 'b', 'c'], ['d', 'b']) + +would return: false +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "member(): Wrong number of arguments " + +      "given (#{arguments.size} for 2)") if arguments.size < 2 + +    array = arguments[0] + +    unless array.is_a?(Array) +      raise(Puppet::ParseError, 'member(): Requires array to work with') +    end + +    unless arguments[1].is_a? String or arguments[1].is_a? Fixnum or arguments[1].is_a? Array +      raise(Puppet::ParseError, 'member(): Item to search for must be a string, fixnum, or array') +    end + +    if arguments[1].is_a? String or arguments[1].is_a? Fixnum +      item = Array(arguments[1]) +    else +      item = arguments[1] +    end + + +    raise(Puppet::ParseError, 'member(): You must provide item ' + +      'to search for within array given') if item.respond_to?('empty?') && item.empty? + +    result = (item - array).empty? + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/merge.rb b/lib/puppet/parser/functions/merge.rb new file mode 100644 index 00000000..1b39f206 --- /dev/null +++ b/lib/puppet/parser/functions/merge.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions +  newfunction(:merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| +    Merges two or more hashes together and returns the resulting hash. + +    For example: + +        $hash1 = {'one' => 1, 'two', => 2} +        $hash2 = {'two' => 'dos', 'three', => 'tres'} +        $merged_hash = merge($hash1, $hash2) +        # The resulting hash is equivalent to: +        # $merged_hash =  {'one' => 1, 'two' => 'dos', 'three' => 'tres'} + +    When there is a duplicate key, the key in the rightmost hash will "win." + +    ENDHEREDOC + +    if args.length < 2 +      raise Puppet::ParseError, ("merge(): wrong number of arguments (#{args.length}; must be at least 2)") +    end + +    # The hash we accumulate into +    accumulator = Hash.new +    # Merge into the accumulator hash +    args.each do |arg| +      next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef +      unless arg.is_a?(Hash) +        raise Puppet::ParseError, "merge: unexpected argument type #{arg.class}, only expects hash arguments" +      end +      accumulator.merge!(arg) +    end +    # Return the fully merged hash +    accumulator +  end +end diff --git a/lib/puppet/parser/functions/min.rb b/lib/puppet/parser/functions/min.rb new file mode 100644 index 00000000..6bd6ebf2 --- /dev/null +++ b/lib/puppet/parser/functions/min.rb @@ -0,0 +1,21 @@ +module Puppet::Parser::Functions +  newfunction(:min, :type => :rvalue, :doc => <<-EOS +    Returns the lowest value of all arguments. +    Requires at least one argument. +    EOS +  ) do |args| + +    raise(Puppet::ParseError, "min(): Wrong number of arguments " + +          "need at least one") if args.size == 0 + +    # Sometimes we get numbers as numerics and sometimes as strings. +    # We try to compare them as numbers when possible +    return args.min do |a,b| +      if a.to_s =~ /\A^-?\d+(.\d+)?\z/ and b.to_s =~ /\A-?\d+(.\d+)?\z/ then +        a.to_f <=> b.to_f +      else +        a.to_s <=> b.to_s +      end +    end +  end +end diff --git a/lib/puppet/parser/functions/num2bool.rb b/lib/puppet/parser/functions/num2bool.rb new file mode 100644 index 00000000..af0e6ed7 --- /dev/null +++ b/lib/puppet/parser/functions/num2bool.rb @@ -0,0 +1,43 @@ +# +# num2bool.rb +# + +module Puppet::Parser::Functions +  newfunction(:num2bool, :type => :rvalue, :doc => <<-EOS +This function converts a number or a string representation of a number into a +true boolean. Zero or anything non-numeric becomes false. Numbers higher then 0 +become true. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "num2bool(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size != 1 + +    number = arguments[0] + +    case number +    when Numeric +      # Yay, it's a number +    when String +      begin +        number = Float(number) +      rescue ArgumentError => ex +        raise(Puppet::ParseError, "num2bool(): '#{number}' does not look like a number: #{ex.message}") +      end +    else +      begin +        number = number.to_s +      rescue NoMethodError => ex +        raise(Puppet::ParseError, "num2bool(): Unable to parse argument: #{ex.message}") +      end +    end + +    # Truncate Floats +    number = number.to_i + +    # Return true for any positive number and false otherwise +    return number > 0 +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/obfuscate_email.rb b/lib/puppet/parser/functions/obfuscate_email.rb new file mode 100644 index 00000000..4e4cb826 --- /dev/null +++ b/lib/puppet/parser/functions/obfuscate_email.rb @@ -0,0 +1,16 @@ +module Puppet::Parser::Functions +  newfunction(:obfuscate_email, :type => :rvalue, :doc => <<-EOS +Given: +  a comma seperated email string in form of 'john@doe.com, doe@john.com' + +This function will return all emails obfuscated in form of 'john {at} doe {dot} com, doe {at} john {dot} com'  +Works with multiple email adresses as well as with a single email adress. + +    EOS +  ) do |args| +      args[0].gsub('@', ' {at} ').gsub('.', ' {dot} ') +    end +end + +# vim: set ts=2 sw=2 et : +# encoding: utf-8 diff --git a/lib/puppet/parser/functions/parsejson.rb b/lib/puppet/parser/functions/parsejson.rb new file mode 100644 index 00000000..a9a16a45 --- /dev/null +++ b/lib/puppet/parser/functions/parsejson.rb @@ -0,0 +1,24 @@ +# +# parsejson.rb +# + +module Puppet::Parser::Functions +  newfunction(:parsejson, :type => :rvalue, :doc => <<-EOS +This function accepts JSON as a string and converts into the correct Puppet +structure. +    EOS +  ) do |arguments| + +    if (arguments.size != 1) then +      raise(Puppet::ParseError, "parsejson(): Wrong number of arguments "+ +        "given #{arguments.size} for 1") +    end + +    json = arguments[0] + +    # PSON is natively available in puppet +    PSON.load(json) +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/parseyaml.rb b/lib/puppet/parser/functions/parseyaml.rb new file mode 100644 index 00000000..53d54faf --- /dev/null +++ b/lib/puppet/parser/functions/parseyaml.rb @@ -0,0 +1,24 @@ +# +# parseyaml.rb +# + +module Puppet::Parser::Functions +  newfunction(:parseyaml, :type => :rvalue, :doc => <<-EOS +This function accepts YAML as a string and converts it into the correct +Puppet structure. +    EOS +  ) do |arguments| + +    if (arguments.size != 1) then +      raise(Puppet::ParseError, "parseyaml(): Wrong number of arguments "+ +        "given #{arguments.size} for 1") +    end + +    require 'yaml' + +    YAML::load(arguments[0]) + +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/pick.rb b/lib/puppet/parser/functions/pick.rb new file mode 100644 index 00000000..fdd0aefd --- /dev/null +++ b/lib/puppet/parser/functions/pick.rb @@ -0,0 +1,29 @@ +module Puppet::Parser::Functions + newfunction(:pick, :type => :rvalue, :doc => <<-EOS + +This function is similar to a coalesce function in SQL in that it will return +the first value in a list of values that is not undefined or an empty string +(two things in Puppet that will return a boolean false value). Typically, +this function is used to check for a value in the Puppet Dashboard/Enterprise +Console, and failover to a default value like the following: + +  $real_jenkins_version = pick($::jenkins_version, '1.449') + +The value of $real_jenkins_version will first look for a top-scope variable +called 'jenkins_version' (note that parameters set in the Puppet Dashboard/ +Enterprise Console are brought into Puppet as top-scope variables), and, +failing that, will use a default value of 1.449. + +EOS +) do |args| +   args = args.compact +   args.delete(:undef) +   args.delete(:undefined) +   args.delete("") +   if args[0].to_s.empty? then +     fail Puppet::ParseError, "pick(): must receive at least one non empty value" +   else +     return args[0] +   end + end +end diff --git a/lib/puppet/parser/functions/pick_default.rb b/lib/puppet/parser/functions/pick_default.rb new file mode 100644 index 00000000..36e33abf --- /dev/null +++ b/lib/puppet/parser/functions/pick_default.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + newfunction(:pick_default, :type => :rvalue, :doc => <<-EOS + +This function is similar to a coalesce function in SQL in that it will return +the first value in a list of values that is not undefined or an empty string +(two things in Puppet that will return a boolean false value). If no value is +found, it will return the last argument. + +Typically, this function is used to check for a value in the Puppet +Dashboard/Enterprise Console, and failover to a default value like the +following: + +  $real_jenkins_version = pick_default($::jenkins_version, '1.449') + +The value of $real_jenkins_version will first look for a top-scope variable +called 'jenkins_version' (note that parameters set in the Puppet Dashboard/ +Enterprise Console are brought into Puppet as top-scope variables), and, +failing that, will use a default value of 1.449. + +Note that, contrary to the pick() function, the pick_default does not fail if +all arguments are empty. This allows pick_default to use an empty value as +default. + +EOS +) do |args| +   fail "Must receive at least one argument." if args.empty? +   default = args.last +   args = args[0..-2].compact +   args.delete(:undef) +   args.delete(:undefined) +   args.delete("") +   args << default +   return args[0] + end +end diff --git a/lib/puppet/parser/functions/prefix.rb b/lib/puppet/parser/functions/prefix.rb new file mode 100644 index 00000000..d02286af --- /dev/null +++ b/lib/puppet/parser/functions/prefix.rb @@ -0,0 +1,45 @@ +# +# prefix.rb +# + +module Puppet::Parser::Functions +  newfunction(:prefix, :type => :rvalue, :doc => <<-EOS +This function applies a prefix to all elements in an array. + +*Examples:* + +    prefix(['a','b','c'], 'p') + +Will return: ['pa','pb','pc'] +    EOS +  ) do |arguments| + +    # Technically we support two arguments but only first is mandatory ... +    raise(Puppet::ParseError, "prefix(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    array = arguments[0] + +    unless array.is_a?(Array) +      raise Puppet::ParseError, "prefix(): expected first argument to be an Array, got #{array.inspect}" +    end + +    prefix = arguments[1] if arguments[1] + +    if prefix +      unless prefix.is_a?(String) +        raise Puppet::ParseError, "prefix(): expected second argument to be a String, got #{prefix.inspect}" +      end +    end + +    # Turn everything into string same as join would do ... +    result = array.collect do |i| +      i = i.to_s +      prefix ? prefix + i : i +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/private.rb b/lib/puppet/parser/functions/private.rb new file mode 100644 index 00000000..60210d33 --- /dev/null +++ b/lib/puppet/parser/functions/private.rb @@ -0,0 +1,29 @@ +# +# private.rb +# + +module Puppet::Parser::Functions +  newfunction(:private, :doc => <<-'EOS' +    Sets the current class or definition as private. +    Calling the class or definition from outside the current module will fail. +    EOS +  ) do |args| + +    raise(Puppet::ParseError, "private(): Wrong number of arguments "+ +      "given (#{args.size}}) for 0 or 1)") if args.size > 1 + +    scope = self +    if scope.lookupvar('module_name') != scope.lookupvar('caller_module_name') +      message = nil +      if args[0] and args[0].is_a? String +        message = args[0] +      else +        manifest_name = scope.source.name +        manifest_type = scope.source.type +        message = (manifest_type.to_s == 'hostclass') ? 'Class' : 'Definition' +        message += " #{manifest_name} is private" +      end +      raise(Puppet::ParseError, message) +    end +  end +end diff --git a/lib/puppet/parser/functions/range.rb b/lib/puppet/parser/functions/range.rb new file mode 100644 index 00000000..49fba21c --- /dev/null +++ b/lib/puppet/parser/functions/range.rb @@ -0,0 +1,88 @@ +# +# range.rb +# + +# TODO(Krzysztof Wilczynski): We probably need to approach numeric values differently ... + +module Puppet::Parser::Functions +  newfunction(:range, :type => :rvalue, :doc => <<-EOS +When given range in the form of (start, stop) it will extrapolate a range as +an array. + +*Examples:* + +    range("0", "9") + +Will return: [0,1,2,3,4,5,6,7,8,9] + +    range("00", "09") + +Will return: [0,1,2,3,4,5,6,7,8,9] (Zero padded strings are converted to +integers automatically) + +    range("a", "c") + +Will return: ["a","b","c"] + +    range("host01", "host10") + +Will return: ["host01", "host02", ..., "host09", "host10"] + +Passing a third argument will cause the generated range to step by that +interval, e.g. + +    range("0", "9", "2") + +Will return: [0,2,4,6,8] +    EOS +  ) do |arguments| + +    # We support more than one argument but at least one is mandatory ... +    raise(Puppet::ParseError, "range(): Wrong number of " + +      "arguments given (#{arguments.size} for 1)") if arguments.size < 1 + +    if arguments.size > 1 +      start = arguments[0] +      stop  = arguments[1] +      step  = arguments[2].nil? ? 1 : arguments[2].to_i.abs + +      type = '..' # We select simplest type for Range available in Ruby ... + +    elsif arguments.size > 0 +      value = arguments[0] + +      if m = value.match(/^(\w+)(\.\.\.?|\-)(\w+)$/) +        start = m[1] +        stop  = m[3] + +        type = m[2] + +      elsif value.match(/^.+$/) +        raise(Puppet::ParseError, 'range(): Unable to compute range ' + +          'from the value given') +      else +        raise(Puppet::ParseError, 'range(): Unknown format of range given') +      end +    end + +    # Check whether we have integer value if so then make it so ... +    if start.to_s.match(/^\d+$/) +      start = start.to_i +      stop  = stop.to_i +    else +      start = start.to_s +      stop  = stop.to_s +    end + +    range = case type +      when /^(\.\.|\-)$/ then (start .. stop) +      when /^(\.\.\.)$/  then (start ... stop) # Exclusive of last element ... +    end + +    result = range.step(step).collect { |i| i } # Get them all ... Pokemon ... + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/reject.rb b/lib/puppet/parser/functions/reject.rb new file mode 100644 index 00000000..1953ffcf --- /dev/null +++ b/lib/puppet/parser/functions/reject.rb @@ -0,0 +1,31 @@ +# +# reject.rb +# + +module Puppet::Parser::Functions +  newfunction(:reject, :type => :rvalue, :doc => <<-EOS) do |args| +This function searches through an array and rejects all elements that match +the provided regular expression. + +*Examples:* + +    reject(['aaa','bbb','ccc','aaaddd'], 'aaa') + +Would return: + +    ['bbb','ccc'] +EOS + +    if (args.size != 2) +      raise Puppet::ParseError, +        "reject(): Wrong number of arguments given #{args.size} for 2" +    end + +    ary = args[0] +    pattern = Regexp.new(args[1]) + +    ary.reject { |e| e =~ pattern } +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/reverse.rb b/lib/puppet/parser/functions/reverse.rb new file mode 100644 index 00000000..7f1018f6 --- /dev/null +++ b/lib/puppet/parser/functions/reverse.rb @@ -0,0 +1,27 @@ +# +# reverse.rb +# + +module Puppet::Parser::Functions +  newfunction(:reverse, :type => :rvalue, :doc => <<-EOS +Reverses the order of a string or array. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "reverse(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'reverse(): Requires either ' + +        'array or string to work with') +    end + +    result = value.reverse + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/rstrip.rb b/lib/puppet/parser/functions/rstrip.rb new file mode 100644 index 00000000..0cf8d222 --- /dev/null +++ b/lib/puppet/parser/functions/rstrip.rb @@ -0,0 +1,31 @@ +# +#  rstrip.rb +# + +module Puppet::Parser::Functions +  newfunction(:rstrip, :type => :rvalue, :doc => <<-EOS +Strips leading spaces to the right of the string. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "rstrip(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'rstrip(): Requires either ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      result = value.collect { |i| i.is_a?(String) ? i.rstrip : i } +    else +      result = value.rstrip +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/shuffle.rb b/lib/puppet/parser/functions/shuffle.rb new file mode 100644 index 00000000..30c663db --- /dev/null +++ b/lib/puppet/parser/functions/shuffle.rb @@ -0,0 +1,45 @@ +# +# shuffle.rb +# + +module Puppet::Parser::Functions +  newfunction(:shuffle, :type => :rvalue, :doc => <<-EOS +Randomizes the order of a string or array elements. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "shuffle(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'shuffle(): Requires either ' + +        'array or string to work with') +    end + +    result = value.clone + +    string = value.is_a?(String) ? true : false + +    # Check whether it makes sense to shuffle ... +    return result if result.size <= 1 + +    # We turn any string value into an array to be able to shuffle ... +    result = string ? result.split('') : result + +    elements = result.size + +    # Simple implementation of Fisher–Yates in-place shuffle ... +    elements.times do |i| +      j = rand(elements - i) + i +      result[j], result[i] = result[i], result[j] +    end + +    result = string ? result.join : result + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/size.rb b/lib/puppet/parser/functions/size.rb new file mode 100644 index 00000000..cc207e3f --- /dev/null +++ b/lib/puppet/parser/functions/size.rb @@ -0,0 +1,48 @@ +# +# size.rb +# + +# TODO(Krzysztof Wilczynski): Support for hashes would be nice too ... + +module Puppet::Parser::Functions +  newfunction(:size, :type => :rvalue, :doc => <<-EOS +Returns the number of elements in a string or array. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "size(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    item = arguments[0] + +    if item.is_a?(String) + +      begin +        # +        # Check whether your item is a numeric value or not ... +        # This will take care about positive and/or negative numbers +        # for both integer and floating-point values ... +        # +        # Please note that Puppet has no notion of hexadecimal +        # nor octal numbers for its DSL at this point in time ... +        # +        Float(item) + +        raise(Puppet::ParseError, 'size(): Requires either ' + +          'string or array to work with') + +      rescue ArgumentError +        result = item.size +      end + +    elsif item.is_a?(Array) +      result = item.size +    else +      raise(Puppet::ParseError, 'size(): Unknown type given') +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/sort.rb b/lib/puppet/parser/functions/sort.rb new file mode 100644 index 00000000..cefbe546 --- /dev/null +++ b/lib/puppet/parser/functions/sort.rb @@ -0,0 +1,27 @@ +# +# sort.rb +# + +module Puppet::Parser::Functions +  newfunction(:sort, :type => :rvalue, :doc => <<-EOS +Sorts strings and arrays lexically. +    EOS +  ) do |arguments| + +    if (arguments.size != 1) then +      raise(Puppet::ParseError, "sort(): Wrong number of arguments "+ +        "given #{arguments.size} for 1") +    end + +    value = arguments[0] + +    if value.is_a?(Array) then +      value.sort +    elsif value.is_a?(String) then +      value.split("").sort.join("") +    end + +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/squeeze.rb b/lib/puppet/parser/functions/squeeze.rb new file mode 100644 index 00000000..81fadfdb --- /dev/null +++ b/lib/puppet/parser/functions/squeeze.rb @@ -0,0 +1,36 @@ +# +# squeeze.rb +# + +module Puppet::Parser::Functions +  newfunction(:squeeze, :type => :rvalue, :doc => <<-EOS +Returns a new string where runs of the same character that occur in this set are replaced by a single character. +    EOS +  ) do |arguments| + +    if ((arguments.size != 2) and (arguments.size != 1)) then +      raise(Puppet::ParseError, "squeeze(): Wrong number of arguments "+ +        "given #{arguments.size} for 2 or 1") +    end + +    item = arguments[0] +    squeezeval = arguments[1] + +    if item.is_a?(Array) then +      if squeezeval then +        item.collect { |i| i.squeeze(squeezeval) } +      else +        item.collect { |i| i.squeeze } +      end +    else +      if squeezeval then +        item.squeeze(squeezeval) +      else +        item.squeeze +      end +    end + +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/str2bool.rb b/lib/puppet/parser/functions/str2bool.rb new file mode 100644 index 00000000..446732ec --- /dev/null +++ b/lib/puppet/parser/functions/str2bool.rb @@ -0,0 +1,46 @@ +# +# str2bool.rb +# + +module Puppet::Parser::Functions +  newfunction(:str2bool, :type => :rvalue, :doc => <<-EOS +This converts a string to a boolean. This attempt to convert strings that +contain things like: y, 1, t, true to 'true' and strings that contain things +like: 0, f, n, false, no to 'false'. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "str2bool(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    string = arguments[0] + +    # If string is already Boolean, return it +    if !!string == string +      return string +    end + +    unless string.is_a?(String) +      raise(Puppet::ParseError, 'str2bool(): Requires either ' + +        'string to work with') +    end + +    # We consider all the yes, no, y, n and so on too ... +    result = case string +      # +      # This is how undef looks like in Puppet ... +      # We yield false in this case. +      # +      when /^$/, '' then false # Empty string will be false ... +      when /^(1|t|y|true|yes)$/  then true +      when /^(0|f|n|false|no)$/  then false +      when /^(undef|undefined)$/ then false # This is not likely to happen ... +      else +        raise(Puppet::ParseError, 'str2bool(): Unknown type of boolean given') +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/str2saltedsha1.rb b/lib/puppet/parser/functions/str2saltedsha1.rb new file mode 100644 index 00000000..e51a861a --- /dev/null +++ b/lib/puppet/parser/functions/str2saltedsha1.rb @@ -0,0 +1,32 @@ +# +# str2saltedsha1.rb +# + +module Puppet::Parser::Functions +  newfunction(:str2saltedsha1, :type => :rvalue, :doc => <<-EOS +This converts a string to a salted-SHA1 password hash (which is used for +OS X versions >= 10.7). Given any simple string, you will get a hex version +of a salted-SHA1 password hash that can be inserted into your Puppet +manifests as a valid password attribute. +    EOS +  ) do |arguments| +    require 'digest/sha2' + +    raise(Puppet::ParseError, "str2saltedsha1(): Wrong number of arguments " + +      "passed (#{arguments.size} but we require 1)") if arguments.size != 1 + +    password = arguments[0] + +    unless password.is_a?(String) +      raise(Puppet::ParseError, 'str2saltedsha1(): Requires a ' + +        "String argument, you passed: #{password.class}") +    end + +    seedint    = rand(2**31 - 1) +    seedstring = Array(seedint).pack("L") +    saltedpass = Digest::SHA1.digest(seedstring + password) +    (seedstring + saltedpass).unpack('H*')[0] +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/str2saltedsha512.rb b/lib/puppet/parser/functions/str2saltedsha512.rb new file mode 100644 index 00000000..7fe7b012 --- /dev/null +++ b/lib/puppet/parser/functions/str2saltedsha512.rb @@ -0,0 +1,32 @@ +# +# str2saltedsha512.rb +# + +module Puppet::Parser::Functions +  newfunction(:str2saltedsha512, :type => :rvalue, :doc => <<-EOS +This converts a string to a salted-SHA512 password hash (which is used for +OS X versions >= 10.7). Given any simple string, you will get a hex version +of a salted-SHA512 password hash that can be inserted into your Puppet +manifests as a valid password attribute. +    EOS +  ) do |arguments| +    require 'digest/sha2' + +    raise(Puppet::ParseError, "str2saltedsha512(): Wrong number of arguments " + +      "passed (#{arguments.size} but we require 1)") if arguments.size != 1 + +    password = arguments[0] + +    unless password.is_a?(String) +      raise(Puppet::ParseError, 'str2saltedsha512(): Requires a ' + +        "String argument, you passed: #{password.class}") +    end + +    seedint    = rand(2**31 - 1) +    seedstring = Array(seedint).pack("L") +    saltedpass = Digest::SHA512.digest(seedstring + password) +    (seedstring + saltedpass).unpack('H*')[0] +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/str2sha1_and_salt.rb b/lib/puppet/parser/functions/str2sha1_and_salt.rb new file mode 100644 index 00000000..9ec382d0 --- /dev/null +++ b/lib/puppet/parser/functions/str2sha1_and_salt.rb @@ -0,0 +1,36 @@ +# +# str2saltedsha1.rb +# + +module Puppet::Parser::Functions +  newfunction(:str2sha1_and_salt, :type => :rvalue, :doc => <<-EOS +This converts a string to an array containing the salted SHA1 password hash in +the first field, and the salt itself in second field of the returned array.  +This combination is used i.e. for couchdb passwords. +    EOS +  ) do |arguments| +    require 'digest/sha1' + +    raise(Puppet::ParseError, "str2saltedsha1(): Wrong number of arguments " + +      "passed (#{arguments.size} but we require 1)") if arguments.size != 1 + +    password = arguments[0] + +    unless password.is_a?(String) +      raise(Puppet::ParseError, 'str2saltedsha1(): Requires a ' + +        "String argument, you passed: #{password.class}") +    end + +    seedint    = rand(2**31 - 1) +    seedstring = Array(seedint).pack("L") +    salt       = Digest::MD5.hexdigest(seedstring) +    saltedpass = Digest::SHA1.hexdigest(password + salt) + +    array = Array.new +    array << saltedpass +    array << salt  +    return array  +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/str_and_salt2sha1.rb b/lib/puppet/parser/functions/str_and_salt2sha1.rb new file mode 100644 index 00000000..71d69cf5 --- /dev/null +++ b/lib/puppet/parser/functions/str_and_salt2sha1.rb @@ -0,0 +1,32 @@ +# +# str_and_salt2sha1.rb  +# + +module Puppet::Parser::Functions +  newfunction(:str_and_salt2sha1, :type => :rvalue, :doc => <<-EOS +This converts a string to an array containing the salted SHA1 password hash in +the first field, and the salt itself in second field of the returned array.  +This combination is used i.e. for couchdb passwords. +    EOS +  ) do |arguments| +    require 'digest/sha1' + +    raise(Puppet::ParseError, "str_and_salt2sha1(): Wrong number of arguments " + +      "passed (#{arguments.size} but we require 1)") if arguments.size != 1 + +    str_and_salt = arguments[0] + +    unless str_and_salt.is_a?(Array) +      raise(Puppet::ParseError, 'str_and_salt2sha1(): Requires a ' + +        "Array argument, you passed: #{password.class}") +    end + +    str  = str_and_salt[0] +    salt = str_and_salt[1] +    sha1 = Digest::SHA1.hexdigest(str+ salt) + +    return sha1 +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/strftime.rb b/lib/puppet/parser/functions/strftime.rb new file mode 100644 index 00000000..0b52adec --- /dev/null +++ b/lib/puppet/parser/functions/strftime.rb @@ -0,0 +1,107 @@ +# +# strftime.rb +# + +module Puppet::Parser::Functions +  newfunction(:strftime, :type => :rvalue, :doc => <<-EOS +This function returns formatted time. + +*Examples:* + +To return the time since epoch: + +    strftime("%s") + +To return the date: + +    strftime("%Y-%m-%d") + +*Format meaning:* + +    %a - The abbreviated weekday name (``Sun'') +    %A - The  full  weekday  name (``Sunday'') +    %b - The abbreviated month name (``Jan'') +    %B - The  full  month  name (``January'') +    %c - The preferred local date and time representation +    %C - Century (20 in 2009) +    %d - Day of the month (01..31) +    %D - Date (%m/%d/%y) +    %e - Day of the month, blank-padded ( 1..31) +    %F - Equivalent to %Y-%m-%d (the ISO 8601 date format) +    %h - Equivalent to %b +    %H - Hour of the day, 24-hour clock (00..23) +    %I - Hour of the day, 12-hour clock (01..12) +    %j - Day of the year (001..366) +    %k - hour, 24-hour clock, blank-padded ( 0..23) +    %l - hour, 12-hour clock, blank-padded ( 0..12) +    %L - Millisecond of the second (000..999) +    %m - Month of the year (01..12) +    %M - Minute of the hour (00..59) +    %n - Newline (\n) +    %N - Fractional seconds digits, default is 9 digits (nanosecond) +            %3N  millisecond (3 digits) +            %6N  microsecond (6 digits) +            %9N  nanosecond (9 digits) +    %p - Meridian indicator (``AM''  or  ``PM'') +    %P - Meridian indicator (``am''  or  ``pm'') +    %r - time, 12-hour (same as %I:%M:%S %p) +    %R - time, 24-hour (%H:%M) +    %s - Number of seconds since 1970-01-01 00:00:00 UTC. +    %S - Second of the minute (00..60) +    %t - Tab character (\t) +    %T - time, 24-hour (%H:%M:%S) +    %u - Day of the week as a decimal, Monday being 1. (1..7) +    %U - Week  number  of the current year, +            starting with the first Sunday as the first +            day of the first week (00..53) +    %v - VMS date (%e-%b-%Y) +    %V - Week number of year according to ISO 8601 (01..53) +    %W - Week  number  of the current year, +            starting with the first Monday as the first +            day of the first week (00..53) +    %w - Day of the week (Sunday is 0, 0..6) +    %x - Preferred representation for the date alone, no time +    %X - Preferred representation for the time alone, no date +    %y - Year without a century (00..99) +    %Y - Year with century +    %z - Time zone as  hour offset from UTC (e.g. +0900) +    %Z - Time zone name +    %% - Literal ``%'' character +    EOS +  ) do |arguments| + +    # Technically we support two arguments but only first is mandatory ... +    raise(Puppet::ParseError, "strftime(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    format = arguments[0] + +    raise(Puppet::ParseError, 'strftime(): You must provide ' + +      'format for evaluation') if format.empty? + +    # The Time Zone argument is optional ... +    time_zone = arguments[1] if arguments[1] + +    time = Time.new + +    # There is probably a better way to handle Time Zone ... +    if time_zone and not time_zone.empty? +      original_zone = ENV['TZ'] + +      local_time = time.clone +      local_time = local_time.utc + +      ENV['TZ'] = time_zone + +      time = local_time.localtime + +      ENV['TZ'] = original_zone +    end + +    result = time.strftime(format) + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/strip.rb b/lib/puppet/parser/functions/strip.rb new file mode 100644 index 00000000..3fac47d5 --- /dev/null +++ b/lib/puppet/parser/functions/strip.rb @@ -0,0 +1,38 @@ +# +#  strip.rb +# + +module Puppet::Parser::Functions +  newfunction(:strip, :type => :rvalue, :doc => <<-EOS +This function removes leading and trailing whitespace from a string or from +every string inside an array. + +*Examples:* + +    strip("    aaa   ") + +Would result in: "aaa" +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "strip(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'strip(): Requires either ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      result = value.collect { |i| i.is_a?(String) ? i.strip : i } +    else +      result = value.strip +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/suffix.rb b/lib/puppet/parser/functions/suffix.rb new file mode 100644 index 00000000..f7792d6f --- /dev/null +++ b/lib/puppet/parser/functions/suffix.rb @@ -0,0 +1,45 @@ +# +# suffix.rb +# + +module Puppet::Parser::Functions +  newfunction(:suffix, :type => :rvalue, :doc => <<-EOS +This function applies a suffix to all elements in an array. + +*Examples:* + +    suffix(['a','b','c'], 'p') + +Will return: ['ap','bp','cp'] +    EOS +  ) do |arguments| + +    # Technically we support two arguments but only first is mandatory ... +    raise(Puppet::ParseError, "suffix(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    array = arguments[0] + +    unless array.is_a?(Array) +      raise Puppet::ParseError, "suffix(): expected first argument to be an Array, got #{array.inspect}" +    end + +    suffix = arguments[1] if arguments[1] + +    if suffix +      unless suffix.is_a? String +        raise Puppet::ParseError, "suffix(): expected second argument to be a String, got #{suffix.inspect}" +      end +    end + +    # Turn everything into string same as join would do ... +    result = array.collect do |i| +      i = i.to_s +      suffix ? i + suffix : i +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/swapcase.rb b/lib/puppet/parser/functions/swapcase.rb new file mode 100644 index 00000000..eb7fe137 --- /dev/null +++ b/lib/puppet/parser/functions/swapcase.rb @@ -0,0 +1,38 @@ +# +#  swapcase.rb +# + +module Puppet::Parser::Functions +  newfunction(:swapcase, :type => :rvalue, :doc => <<-EOS +This function will swap the existing case of a string. + +*Examples:* + +    swapcase("aBcD") + +Would result in: "AbCd" +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "swapcase(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'swapcase(): Requires either ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      # Numbers in Puppet are often string-encoded which is troublesome ... +      result = value.collect { |i| i.is_a?(String) ? i.swapcase : i } +    else +      result = value.swapcase +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/time.rb b/lib/puppet/parser/functions/time.rb new file mode 100644 index 00000000..0cddaf86 --- /dev/null +++ b/lib/puppet/parser/functions/time.rb @@ -0,0 +1,49 @@ +# +# time.rb +# + +module Puppet::Parser::Functions +  newfunction(:time, :type => :rvalue, :doc => <<-EOS +This function will return the current time since epoch as an integer. + +*Examples:* + +    time() + +Will return something like: 1311972653 +    EOS +  ) do |arguments| + +    # The Time Zone argument is optional ... +    time_zone = arguments[0] if arguments[0] + +    if (arguments.size != 0) and (arguments.size != 1) then +      raise(Puppet::ParseError, "time(): Wrong number of arguments "+ +        "given #{arguments.size} for 0 or 1") +    end + +    time = Time.new + +    # There is probably a better way to handle Time Zone ... +    if time_zone and not time_zone.empty? +      original_zone = ENV['TZ'] + +      local_time = time.clone +      local_time = local_time.utc + +      ENV['TZ'] = time_zone + +      time = local_time.localtime + +      ENV['TZ'] = original_zone +    end + +    # Calling Time#to_i on a receiver changes it.  Trust me I am the Doctor. +    result = time.strftime('%s') +    result = result.to_i + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/to_bytes.rb b/lib/puppet/parser/functions/to_bytes.rb new file mode 100644 index 00000000..df490ea8 --- /dev/null +++ b/lib/puppet/parser/functions/to_bytes.rb @@ -0,0 +1,31 @@ +module Puppet::Parser::Functions +  newfunction(:to_bytes, :type => :rvalue, :doc => <<-EOS +    Converts the argument into bytes, for example 4 kB becomes 4096. +    Takes a single string value as an argument. +    These conversions reflect a layperson's understanding of +    1 MB = 1024 KB, when in fact 1 MB = 1000 KB, and 1 MiB = 1024 KiB. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "to_bytes(): Wrong number of arguments " + +          "given (#{arguments.size} for 1)") if arguments.size != 1 + +    arg = arguments[0] + +    return arg if arg.is_a? Numeric + +    value,prefix = */([0-9.e+-]*)\s*([^bB]?)/.match(arg)[1,2] + +    value = value.to_f +    case prefix +    when '' then return value.to_i +    when 'k' then return (value*(1<<10)).to_i +    when 'M' then return (value*(1<<20)).to_i +    when 'G' then return (value*(1<<30)).to_i +    when 'T' then return (value*(1<<40)).to_i +    when 'P' then return (value*(1<<50)).to_i +    when 'E' then return (value*(1<<60)).to_i +    else raise Puppet::ParseError, "to_bytes(): Unknown prefix #{prefix}" +    end +  end +end diff --git a/lib/puppet/parser/functions/type.rb b/lib/puppet/parser/functions/type.rb new file mode 100644 index 00000000..016529b0 --- /dev/null +++ b/lib/puppet/parser/functions/type.rb @@ -0,0 +1,19 @@ +# +# type.rb +# + +module Puppet::Parser::Functions +  newfunction(:type, :type => :rvalue, :doc => <<-EOS +  DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system. +    EOS +  ) do |args| + +    warning("type() DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.") +    if ! Puppet::Parser::Functions.autoloader.loaded?(:type3x) +      Puppet::Parser::Functions.autoloader.load(:type3x) +    end +    function_type3x(args + [false]) +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/type3x.rb b/lib/puppet/parser/functions/type3x.rb new file mode 100644 index 00000000..0800b4a3 --- /dev/null +++ b/lib/puppet/parser/functions/type3x.rb @@ -0,0 +1,51 @@ +# +# type3x.rb +# + +module Puppet::Parser::Functions +  newfunction(:type3x, :type => :rvalue, :doc => <<-EOS +DEPRECATED: This function will be removed when puppet 3 support is dropped; please migrate to the new parser's typing system. + +Returns the type when passed a value. Type can be one of: + +* string +* array +* hash +* float +* integer +* boolean +    EOS +  ) do |args| +    raise(Puppet::ParseError, "type3x(): Wrong number of arguments " + +      "given (#{args.size} for 1)") if args.size < 1 + +    value = args[0] + +    klass = value.class + +    if not [TrueClass, FalseClass, Array, Bignum, Fixnum, Float, Hash, String].include?(klass) +      raise(Puppet::ParseError, 'type3x(): Unknown type') +    end + +    klass = klass.to_s # Ugly ... + +    # We note that Integer is the parent to Bignum and Fixnum ... +    result = case klass +      when /^(?:Big|Fix)num$/ then 'integer' +      when /^(?:True|False)Class$/ then 'boolean' +      else klass +    end + +    if result == "String" then +      if value == value.to_i.to_s then +        result = "Integer" +      elsif value == value.to_f.to_s then +        result = "Float" +      end +    end + +    return result.downcase +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/union.rb b/lib/puppet/parser/functions/union.rb new file mode 100644 index 00000000..c91bb805 --- /dev/null +++ b/lib/puppet/parser/functions/union.rb @@ -0,0 +1,34 @@ +# +# union.rb +# + +module Puppet::Parser::Functions +  newfunction(:union, :type => :rvalue, :doc => <<-EOS +This function returns a union of two arrays. + +*Examples:* + +    union(["a","b","c"],["b","c","d"]) + +Would return: ["a","b","c","d"] +    EOS +  ) do |arguments| + +    # Two arguments are required +    raise(Puppet::ParseError, "union(): Wrong number of arguments " + +      "given (#{arguments.size} for 2)") if arguments.size != 2 + +    first = arguments[0] +    second = arguments[1] + +    unless first.is_a?(Array) && second.is_a?(Array) +      raise(Puppet::ParseError, 'union(): Requires 2 arrays') +    end + +    result = first | second + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/unique.rb b/lib/puppet/parser/functions/unique.rb new file mode 100644 index 00000000..cf770f3b --- /dev/null +++ b/lib/puppet/parser/functions/unique.rb @@ -0,0 +1,50 @@ +# +# unique.rb +# + +module Puppet::Parser::Functions +  newfunction(:unique, :type => :rvalue, :doc => <<-EOS +This function will remove duplicates from strings and arrays. + +*Examples:* + +    unique("aabbcc") + +Will return: + +    abc + +You can also use this with arrays: + +    unique(["a","a","b","b","c","c"]) + +This returns: + +    ["a","b","c"] +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "unique(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'unique(): Requires either ' + +        'array or string to work with') +    end + +    result = value.clone + +    string = value.is_a?(String) ? true : false + +    # We turn any string value into an array to be able to shuffle ... +    result = string ? result.split('') : result +    result = result.uniq # Remove duplicates ... +    result = string ? result.join : result + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/upcase.rb b/lib/puppet/parser/functions/upcase.rb new file mode 100644 index 00000000..4302b29e --- /dev/null +++ b/lib/puppet/parser/functions/upcase.rb @@ -0,0 +1,40 @@ +# +# upcase.rb +# + +module Puppet::Parser::Functions +  newfunction(:upcase, :type => :rvalue, :doc => <<-EOS +Converts a string or an array of strings to uppercase. + +*Examples:* + +    upcase("abcd") + +Will return: + +    ASDF +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "upcase(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'upcase(): Requires either ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      # Numbers in Puppet are often string-encoded which is troublesome ... +      result = value.collect { |i| i.is_a?(String) ? i.upcase : i } +    else +      result = value.upcase +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/uriescape.rb b/lib/puppet/parser/functions/uriescape.rb new file mode 100644 index 00000000..a486eee5 --- /dev/null +++ b/lib/puppet/parser/functions/uriescape.rb @@ -0,0 +1,34 @@ +# +#  uriescape.rb +# +require 'uri' + +module Puppet::Parser::Functions +  newfunction(:uriescape, :type => :rvalue, :doc => <<-EOS +    Urlencodes a string or array of strings. +    Requires either a single string or an array as an input. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "uriescape(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    value = arguments[0] + +    unless value.is_a?(Array) || value.is_a?(String) +      raise(Puppet::ParseError, 'uriescape(): Requires either ' + +        'array or string to work with') +    end + +    if value.is_a?(Array) +      # Numbers in Puppet are often string-encoded which is troublesome ... +      result = value.collect { |i| i.is_a?(String) ? URI.escape(i,unsafe) : i } +    else +      result = URI.escape(value) +    end + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/validate_absolute_path.rb b/lib/puppet/parser/functions/validate_absolute_path.rb new file mode 100644 index 00000000..b6966809 --- /dev/null +++ b/lib/puppet/parser/functions/validate_absolute_path.rb @@ -0,0 +1,69 @@ +module Puppet::Parser::Functions +  newfunction(:validate_absolute_path, :doc => <<-'ENDHEREDOC') do |args| +    Validate the string represents an absolute path in the filesystem.  This function works +    for windows and unix style paths. + +    The following values will pass: + +        $my_path = 'C:/Program Files (x86)/Puppet Labs/Puppet' +        validate_absolute_path($my_path) +        $my_path2 = '/var/lib/puppet' +        validate_absolute_path($my_path2) +        $my_path3 = ['C:/Program Files (x86)/Puppet Labs/Puppet','C:/Program Files/Puppet Labs/Puppet'] +        validate_absolute_path($my_path3) +        $my_path4 = ['/var/lib/puppet','/usr/share/puppet'] +        validate_absolute_path($my_path4) + +    The following values will fail, causing compilation to abort: + +        validate_absolute_path(true) +        validate_absolute_path('../var/lib/puppet') +        validate_absolute_path('var/lib/puppet') +        validate_absolute_path([ 'var/lib/puppet', '/var/foo' ]) +        validate_absolute_path([ '/var/lib/puppet', 'var/foo' ]) +        $undefined = undef +        validate_absolute_path($undefined) + +    ENDHEREDOC + +    require 'puppet/util' + +    unless args.length > 0 then +      raise Puppet::ParseError, ("validate_absolute_path(): wrong number of arguments (#{args.length}; must be > 0)") +    end + +    args.each do |arg| +      # put arg to candidate var to be able to replace it +      candidates = arg +      # if arg is just a string with a path to test, convert it to an array +      # to avoid test code duplication +      unless arg.is_a?(Array) then +        candidates = Array.new(1,arg) +      end +      # iterate over all pathes within the candidates array +      candidates.each do |path| +        # This logic was borrowed from +        # [lib/puppet/file_serving/base.rb](https://github.com/puppetlabs/puppet/blob/master/lib/puppet/file_serving/base.rb) +        # Puppet 2.7 and beyond will have Puppet::Util.absolute_path? Fall back to a back-ported implementation otherwise. +        if Puppet::Util.respond_to?(:absolute_path?) then +          unless Puppet::Util.absolute_path?(path, :posix) or Puppet::Util.absolute_path?(path, :windows) +            raise Puppet::ParseError, ("#{path.inspect} is not an absolute path.") +          end +        else +          # This code back-ported from 2.7.x's lib/puppet/util.rb Puppet::Util.absolute_path? +          # Determine in a platform-specific way whether a path is absolute. This +          # defaults to the local platform if none is specified. +          # Escape once for the string literal, and once for the regex. +          slash = '[\\\\/]' +          name = '[^\\\\/]+' +          regexes = { +            :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, +            :posix => %r!^/!, +          } +          rval = (!!(path =~ regexes[:posix])) || (!!(path =~ regexes[:windows])) +          rval or raise Puppet::ParseError, ("#{path.inspect} is not an absolute path.") +        end +      end +    end +  end +end diff --git a/lib/puppet/parser/functions/validate_array.rb b/lib/puppet/parser/functions/validate_array.rb new file mode 100644 index 00000000..34b51182 --- /dev/null +++ b/lib/puppet/parser/functions/validate_array.rb @@ -0,0 +1,33 @@ +module Puppet::Parser::Functions + +  newfunction(:validate_array, :doc => <<-'ENDHEREDOC') do |args| +    Validate that all passed values are array data structures. Abort catalog +    compilation if any value fails this check. + +    The following values will pass: + +        $my_array = [ 'one', 'two' ] +        validate_array($my_array) + +    The following values will fail, causing compilation to abort: + +        validate_array(true) +        validate_array('some_string') +        $undefined = undef +        validate_array($undefined) + +    ENDHEREDOC + +    unless args.length > 0 then +      raise Puppet::ParseError, ("validate_array(): wrong number of arguments (#{args.length}; must be > 0)") +    end + +    args.each do |arg| +      unless arg.is_a?(Array) +        raise Puppet::ParseError, ("#{arg.inspect} is not an Array.  It looks to be a #{arg.class}") +      end +    end + +  end + +end diff --git a/lib/puppet/parser/functions/validate_augeas.rb b/lib/puppet/parser/functions/validate_augeas.rb new file mode 100644 index 00000000..4ea4fe07 --- /dev/null +++ b/lib/puppet/parser/functions/validate_augeas.rb @@ -0,0 +1,83 @@ +require 'tempfile' + +module Puppet::Parser::Functions +  newfunction(:validate_augeas, :doc => <<-'ENDHEREDOC') do |args| +    Perform validation of a string using an Augeas lens +    The first argument of this function should be a string to +    test, and the second argument should be the name of the Augeas lens to use. +    If Augeas fails to parse the string with the lens, the compilation will +    abort with a parse error. + +    A third argument can be specified, listing paths which should +    not be found in the file. The `$file` variable points to the location +    of the temporary file being tested in the Augeas tree. + +    For example, if you want to make sure your passwd content never contains +    a user `foo`, you could write: + +        validate_augeas($passwdcontent, 'Passwd.lns', ['$file/foo']) + +    Or if you wanted to ensure that no users used the '/bin/barsh' shell, +    you could use: + +        validate_augeas($passwdcontent, 'Passwd.lns', ['$file/*[shell="/bin/barsh"]'] + +    If a fourth argument is specified, this will be the error message raised and +    seen by the user. + +    A helpful error message can be returned like this: + +        validate_augeas($sudoerscontent, 'Sudoers.lns', [], 'Failed to validate sudoers content with Augeas') + +    ENDHEREDOC +    unless Puppet.features.augeas? +      raise Puppet::ParseError, ("validate_augeas(): this function requires the augeas feature. See http://projects.puppetlabs.com/projects/puppet/wiki/Puppet_Augeas#Pre-requisites for how to activate it.") +    end + +    if (args.length < 2) or (args.length > 4) then +      raise Puppet::ParseError, ("validate_augeas(): wrong number of arguments (#{args.length}; must be 2, 3, or 4)") +    end + +    msg = args[3] || "validate_augeas(): Failed to validate content against #{args[1].inspect}" + +    require 'augeas' +    aug = Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD) +    begin +      content = args[0] + +      # Test content in a temporary file +      tmpfile = Tempfile.new("validate_augeas") +      begin +        tmpfile.write(content) +      ensure +        tmpfile.close +      end + +      # Check for syntax +      lens = args[1] +      aug.transform( +        :lens => lens, +        :name => 'Validate_augeas', +        :incl => tmpfile.path +      ) +      aug.load! + +      unless aug.match("/augeas/files#{tmpfile.path}//error").empty? +        error = aug.get("/augeas/files#{tmpfile.path}//error/message") +        msg += " with error: #{error}" +        raise Puppet::ParseError, (msg) +      end + +      # Launch unit tests +      tests = args[2] || [] +      aug.defvar('file', "/files#{tmpfile.path}") +      tests.each do |t| +        msg += " testing path #{t}" +        raise Puppet::ParseError, (msg) unless aug.match(t).empty? +      end +    ensure +      aug.close +      tmpfile.unlink +    end +  end +end diff --git a/lib/puppet/parser/functions/validate_bool.rb b/lib/puppet/parser/functions/validate_bool.rb new file mode 100644 index 00000000..59a08056 --- /dev/null +++ b/lib/puppet/parser/functions/validate_bool.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions + +  newfunction(:validate_bool, :doc => <<-'ENDHEREDOC') do |args| +    Validate that all passed values are either true or false. Abort catalog +    compilation if any value fails this check. + +    The following values will pass: + +        $iamtrue = true +        validate_bool(true) +        validate_bool(true, true, false, $iamtrue) + +    The following values will fail, causing compilation to abort: + +        $some_array = [ true ] +        validate_bool("false") +        validate_bool("true") +        validate_bool($some_array) + +    ENDHEREDOC + +    unless args.length > 0 then +      raise Puppet::ParseError, ("validate_bool(): wrong number of arguments (#{args.length}; must be > 0)") +    end + +    args.each do |arg| +      unless function_is_bool([arg]) +        raise Puppet::ParseError, ("#{arg.inspect} is not a boolean.  It looks to be a #{arg.class}") +      end +    end + +  end + +end diff --git a/lib/puppet/parser/functions/validate_cmd.rb b/lib/puppet/parser/functions/validate_cmd.rb new file mode 100644 index 00000000..5df3c609 --- /dev/null +++ b/lib/puppet/parser/functions/validate_cmd.rb @@ -0,0 +1,63 @@ +require 'puppet/util/execution' +require 'tempfile' + +module Puppet::Parser::Functions +  newfunction(:validate_cmd, :doc => <<-'ENDHEREDOC') do |args| +    Perform validation of a string with an external command. +    The first argument of this function should be a string to +    test, and the second argument should be a path to a test command +    taking a % as a placeholder for the file path (will default to the end). +    If the command, launched against a tempfile containing the passed string, +    returns a non-null value, compilation will abort with a parse error. + +    If a third argument is specified, this will be the error message raised and +    seen by the user. + +    A helpful error message can be returned like this: + +    Example: + +        # Defaults to end of path +        validate_cmd($sudoerscontent, '/usr/sbin/visudo -c -f', 'Visudo failed to validate sudoers content') + +        # % as file location +        validate_cmd($haproxycontent, '/usr/sbin/haproxy -f % -c', 'Haproxy failed to validate config content') + +    ENDHEREDOC +    if (args.length < 2) or (args.length > 3) then +      raise Puppet::ParseError, ("validate_cmd(): wrong number of arguments (#{args.length}; must be 2 or 3)") +    end + +    msg = args[2] || "validate_cmd(): failed to validate content with command #{args[1].inspect}" + +    content = args[0] +    checkscript = args[1] + +    # Test content in a temporary file +    tmpfile = Tempfile.new("validate_cmd") +    begin +      tmpfile.write(content) +      tmpfile.close + +      if checkscript =~ /\s%(\s|$)/ +        check_with_correct_location = checkscript.gsub(/%/,tmpfile.path) +      else +        check_with_correct_location = "#{checkscript} #{tmpfile.path}" +      end + +      if Puppet::Util::Execution.respond_to?('execute') +        Puppet::Util::Execution.execute(check_with_correct_location) +      else +        Puppet::Util.execute(check_with_correct_location) +      end +    rescue Puppet::ExecutionFailure => detail +      msg += "\n#{detail}" +      raise Puppet::ParseError, msg +    rescue Exception => detail +      msg += "\n#{detail.class.name} #{detail}" +      raise Puppet::ParseError, msg +    ensure +      tmpfile.unlink +    end +  end +end diff --git a/lib/puppet/parser/functions/validate_hash.rb b/lib/puppet/parser/functions/validate_hash.rb new file mode 100644 index 00000000..9bdd5432 --- /dev/null +++ b/lib/puppet/parser/functions/validate_hash.rb @@ -0,0 +1,33 @@ +module Puppet::Parser::Functions + +  newfunction(:validate_hash, :doc => <<-'ENDHEREDOC') do |args| +    Validate that all passed values are hash data structures. Abort catalog +    compilation if any value fails this check. + +    The following values will pass: + +        $my_hash = { 'one' => 'two' } +        validate_hash($my_hash) + +    The following values will fail, causing compilation to abort: + +        validate_hash(true) +        validate_hash('some_string') +        $undefined = undef +        validate_hash($undefined) + +    ENDHEREDOC + +    unless args.length > 0 then +      raise Puppet::ParseError, ("validate_hash(): wrong number of arguments (#{args.length}; must be > 0)") +    end + +    args.each do |arg| +      unless arg.is_a?(Hash) +        raise Puppet::ParseError, ("#{arg.inspect} is not a Hash.  It looks to be a #{arg.class}") +      end +    end + +  end + +end diff --git a/lib/puppet/parser/functions/validate_ipv4_address.rb b/lib/puppet/parser/functions/validate_ipv4_address.rb new file mode 100644 index 00000000..fc02748e --- /dev/null +++ b/lib/puppet/parser/functions/validate_ipv4_address.rb @@ -0,0 +1,48 @@ +module Puppet::Parser::Functions + +  newfunction(:validate_ipv4_address, :doc => <<-ENDHEREDOC +    Validate that all values passed are valid IPv4 addresses. +    Fail compilation if any value fails this check. + +    The following values will pass: + +    $my_ip = "1.2.3.4" +    validate_ipv4_address($my_ip) +    validate_bool("8.8.8.8", "172.16.0.1", $my_ip) + +    The following values will fail, causing compilation to abort: + +    $some_array = [ 1, true, false, "garbage string", "3ffe:505:2" ] +    validate_ipv4_address($some_array) + +    ENDHEREDOC +  ) do |args| + +    require "ipaddr" +    rescuable_exceptions = [ ArgumentError ] + +    if defined?(IPAddr::InvalidAddressError) +      rescuable_exceptions << IPAddr::InvalidAddressError +    end + +    unless args.length > 0 then +      raise Puppet::ParseError, ("validate_ipv4_address(): wrong number of arguments (#{args.length}; must be > 0)") +    end + +    args.each do |arg| +      unless arg.is_a?(String) +        raise Puppet::ParseError, "#{arg.inspect} is not a string." +      end + +      begin +        unless IPAddr.new(arg).ipv4? +          raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address." +        end +      rescue *rescuable_exceptions +        raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address." +      end +    end + +  end + +end diff --git a/lib/puppet/parser/functions/validate_ipv6_address.rb b/lib/puppet/parser/functions/validate_ipv6_address.rb new file mode 100644 index 00000000..b0f2558d --- /dev/null +++ b/lib/puppet/parser/functions/validate_ipv6_address.rb @@ -0,0 +1,49 @@ +module Puppet::Parser::Functions + +  newfunction(:validate_ipv6_address, :doc => <<-ENDHEREDOC +    Validate that all values passed are valid IPv6 addresses. +    Fail compilation if any value fails this check. + +    The following values will pass: + +    $my_ip = "3ffe:505:2" +    validate_ipv6_address(1) +    validate_ipv6_address($my_ip) +    validate_bool("fe80::baf6:b1ff:fe19:7507", $my_ip) + +    The following values will fail, causing compilation to abort: + +    $some_array = [ true, false, "garbage string", "1.2.3.4" ] +    validate_ipv6_address($some_array) + +    ENDHEREDOC +  ) do |args| + +    require "ipaddr" +    rescuable_exceptions = [ ArgumentError ] + +    if defined?(IPAddr::InvalidAddressError) +      rescuable_exceptions << IPAddr::InvalidAddressError +    end + +    unless args.length > 0 then +      raise Puppet::ParseError, ("validate_ipv6_address(): wrong number of arguments (#{args.length}; must be > 0)") +    end + +    args.each do |arg| +      unless arg.is_a?(String) +        raise Puppet::ParseError, "#{arg.inspect} is not a string." +      end + +      begin +        unless IPAddr.new(arg).ipv6? +          raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address." +        end +      rescue *rescuable_exceptions +        raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address." +      end +    end + +  end + +end diff --git a/lib/puppet/parser/functions/validate_re.rb b/lib/puppet/parser/functions/validate_re.rb new file mode 100644 index 00000000..ca25a702 --- /dev/null +++ b/lib/puppet/parser/functions/validate_re.rb @@ -0,0 +1,40 @@ +module Puppet::Parser::Functions +  newfunction(:validate_re, :doc => <<-'ENDHEREDOC') do |args| +    Perform simple validation of a string against one or more regular +    expressions. The first argument of this function should be a string to +    test, and the second argument should be a stringified regular expression +    (without the // delimiters) or an array of regular expressions.  If none +    of the regular expressions match the string passed in, compilation will +    abort with a parse error. + +    If a third argument is specified, this will be the error message raised and +    seen by the user. + +    The following strings will validate against the regular expressions: + +        validate_re('one', '^one$') +        validate_re('one', [ '^one', '^two' ]) + +    The following strings will fail to validate, causing compilation to abort: + +        validate_re('one', [ '^two', '^three' ]) + +    A helpful error message can be returned like this: + +        validate_re($::puppetversion, '^2.7', 'The $puppetversion fact value does not match 2.7') + +    ENDHEREDOC +    if (args.length < 2) or (args.length > 3) then +      raise Puppet::ParseError, ("validate_re(): wrong number of arguments (#{args.length}; must be 2 or 3)") +    end + +    msg = args[2] || "validate_re(): #{args[0].inspect} does not match #{args[1].inspect}" + +    # We're using a flattened array here because we can't call String#any? in +    # Ruby 1.9 like we can in Ruby 1.8 +    raise Puppet::ParseError, (msg) unless [args[1]].flatten.any? do |re_str| +      args[0] =~ Regexp.compile(re_str) +    end + +  end +end diff --git a/lib/puppet/parser/functions/validate_slength.rb b/lib/puppet/parser/functions/validate_slength.rb new file mode 100644 index 00000000..7d534f37 --- /dev/null +++ b/lib/puppet/parser/functions/validate_slength.rb @@ -0,0 +1,71 @@ +module Puppet::Parser::Functions + +  newfunction(:validate_slength, :doc => <<-'ENDHEREDOC') do |args| +    Validate that the first argument is a string (or an array of strings), and +    less/equal to than the length of the second argument. An optional third +    parameter can be given a the minimum length. It fails if the first +    argument is not a string or array of strings, and if arg 2 and arg 3 are +    not convertable to a number. + +    The following values will pass: + +      validate_slength("discombobulate",17) +      validate_slength(["discombobulate","moo"],17) +      validate_slength(["discombobulate","moo"],17,3) + +    The following valueis will not: + +      validate_slength("discombobulate",1) +      validate_slength(["discombobulate","thermometer"],5) +      validate_slength(["discombobulate","moo"],17,10) + +    ENDHEREDOC + +    raise Puppet::ParseError, "validate_slength(): Wrong number of arguments (#{args.length}; must be 2 or 3)" unless args.length == 2 or args.length == 3 + +    input, max_length, min_length = *args + +    begin +      max_length = Integer(max_length) +      raise ArgumentError if max_length <= 0 +    rescue ArgumentError, TypeError +      raise Puppet::ParseError, "validate_slength(): Expected second argument to be a positive Numeric, got #{max_length}:#{max_length.class}" +    end + +    if min_length +      begin +        min_length = Integer(min_length) +        raise ArgumentError if min_length < 0 +    rescue ArgumentError, TypeError +        raise Puppet::ParseError, "validate_slength(): Expected third argument to be unset or a positive Numeric, got #{min_length}:#{min_length.class}" +      end +    else +      min_length = 0 +    end + +    if min_length > max_length +      raise Puppet::ParseError, "validate_slength(): Expected second argument to be larger than third argument" +    end + +    validator = lambda do |str| +      unless str.length <= max_length and str.length >= min_length +        raise Puppet::ParseError, "validate_slength(): Expected length of #{input.inspect} to be between #{min_length} and #{max_length}, was #{input.length}" +      end +    end + +    case input +    when String +      validator.call(input) +    when Array +      input.each_with_index do |arg, pos| +        if arg.is_a? String +          validator.call(arg) +        else +          raise Puppet::ParseError, "validate_slength(): Expected element at array position #{pos} to be a String, got #{arg.class}" +        end +      end +    else +      raise Puppet::ParseError, "validate_slength(): Expected first argument to be a String or Array, got #{input.class}" +    end +  end +end diff --git a/lib/puppet/parser/functions/validate_string.rb b/lib/puppet/parser/functions/validate_string.rb new file mode 100644 index 00000000..c841f6ab --- /dev/null +++ b/lib/puppet/parser/functions/validate_string.rb @@ -0,0 +1,38 @@ +module Puppet::Parser::Functions + +  newfunction(:validate_string, :doc => <<-'ENDHEREDOC') do |args| +    Validate that all passed values are string data structures. Abort catalog +    compilation if any value fails this check. + +    The following values will pass: + +        $my_string = "one two" +        validate_string($my_string, 'three') + +    The following values will fail, causing compilation to abort: + +        validate_string(true) +        validate_string([ 'some', 'array' ]) +         +    Note: validate_string(undef) will not fail in this version of the +    functions API (incl. current and future parser). Instead, use: +     +        if $var == undef { +          fail('...') +        } +     +    ENDHEREDOC + +    unless args.length > 0 then +      raise Puppet::ParseError, ("validate_string(): wrong number of arguments (#{args.length}; must be > 0)") +    end + +    args.each do |arg| +      unless arg.is_a?(String) +        raise Puppet::ParseError, ("#{arg.inspect} is not a string.  It looks to be a #{arg.class}") +      end +    end + +  end + +end diff --git a/lib/puppet/parser/functions/values.rb b/lib/puppet/parser/functions/values.rb new file mode 100644 index 00000000..16067561 --- /dev/null +++ b/lib/puppet/parser/functions/values.rb @@ -0,0 +1,39 @@ +# +# values.rb +# + +module Puppet::Parser::Functions +  newfunction(:values, :type => :rvalue, :doc => <<-EOS +When given a hash this function will return the values of that hash. + +*Examples:* + +    $hash = { +      'a' => 1, +      'b' => 2, +      'c' => 3, +    } +    values($hash) + +This example would return: + +    [1,2,3] +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "values(): Wrong number of arguments " + +      "given (#{arguments.size} for 1)") if arguments.size < 1 + +    hash = arguments[0] + +    unless hash.is_a?(Hash) +      raise(Puppet::ParseError, 'values(): Requires hash to work with') +    end + +    result = hash.values + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/values_at.rb b/lib/puppet/parser/functions/values_at.rb new file mode 100644 index 00000000..f350f539 --- /dev/null +++ b/lib/puppet/parser/functions/values_at.rb @@ -0,0 +1,99 @@ +# +# values_at.rb +# + +module Puppet::Parser::Functions +  newfunction(:values_at, :type => :rvalue, :doc => <<-EOS +Finds value inside an array based on location. + +The first argument is the array you want to analyze, and the second element can +be a combination of: + +* A single numeric index +* A range in the form of 'start-stop' (eg. 4-9) +* An array combining the above + +*Examples*: + +    values_at(['a','b','c'], 2) + +Would return ['c']. + +    values_at(['a','b','c'], ["0-1"]) + +Would return ['a','b']. + +    values_at(['a','b','c','d','e'], [0, "2-3"]) + +Would return ['a','c','d']. +    EOS +  ) do |arguments| + +    raise(Puppet::ParseError, "values_at(): Wrong number of " + +      "arguments given (#{arguments.size} for 2)") if arguments.size < 2 + +    array = arguments.shift + +    unless array.is_a?(Array) +      raise(Puppet::ParseError, 'values_at(): Requires array to work with') +    end + +    indices = [arguments.shift].flatten() # Get them all ... Pokemon ... + +    if not indices or indices.empty? +      raise(Puppet::ParseError, 'values_at(): You must provide ' + +        'at least one positive index to collect') +    end + +    result       = [] +    indices_list = [] + +    indices.each do |i| +      i = i.to_s +      if m = i.match(/^(\d+)(\.\.\.?|\-)(\d+)$/) +        start = m[1].to_i +        stop  = m[3].to_i + +        type = m[2] + +        if start > stop +          raise(Puppet::ParseError, 'values_at(): Stop index in ' + +            'given indices range is smaller than the start index') +        elsif stop > array.size - 1 # First element is at index 0 is it not? +          raise(Puppet::ParseError, 'values_at(): Stop index in ' + +            'given indices range exceeds array size') +        end + +        range = case type +          when /^(\.\.|\-)$/ then (start .. stop) +          when /^(\.\.\.)$/  then (start ... stop) # Exclusive of last element ... +        end + +        range.each { |i| indices_list << i.to_i } +      else +        # Only positive numbers allowed in this case ... +        if not i.match(/^\d+$/) +          raise(Puppet::ParseError, 'values_at(): Unknown format ' + +            'of given index') +        end + +        # In Puppet numbers are often string-encoded ... +        i = i.to_i + +        if i > array.size - 1 # Same story.  First element is at index 0 ... +          raise(Puppet::ParseError, 'values_at(): Given index ' + +            'exceeds array size') +        end + +        indices_list << i +      end +    end + +    # We remove nil values as they make no sense in Puppet DSL ... +    result = indices_list.collect { |i| array[i] }.compact + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/zip.rb b/lib/puppet/parser/functions/zip.rb new file mode 100644 index 00000000..3074f282 --- /dev/null +++ b/lib/puppet/parser/functions/zip.rb @@ -0,0 +1,39 @@ +# +# zip.rb +# + +module Puppet::Parser::Functions +  newfunction(:zip, :type => :rvalue, :doc => <<-EOS +Takes one element from first array and merges corresponding elements from second array. This generates a sequence of n-element arrays, where n is one more than the count of arguments. + +*Example:* + +    zip(['1','2','3'],['4','5','6']) + +Would result in: + +    ["1", "4"], ["2", "5"], ["3", "6"] +    EOS +  ) do |arguments| + +    # Technically we support three arguments but only first is mandatory ... +    raise(Puppet::ParseError, "zip(): Wrong number of arguments " + +      "given (#{arguments.size} for 2)") if arguments.size < 2 + +    a = arguments[0] +    b = arguments[1] + +    unless a.is_a?(Array) and b.is_a?(Array) +      raise(Puppet::ParseError, 'zip(): Requires array to work with') +    end + +    flatten = function_str2bool([arguments[2]]) if arguments[2] + +    result = a.zip(b) +    result = flatten ? result.flatten : result + +    return result +  end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/provider/file_line/ruby.rb b/lib/puppet/provider/file_line/ruby.rb new file mode 100644 index 00000000..ae1a8b3d --- /dev/null +++ b/lib/puppet/provider/file_line/ruby.rb @@ -0,0 +1,85 @@ +Puppet::Type.type(:file_line).provide(:ruby) do +  def exists? +    lines.find do |line| +      line.chomp == resource[:line].chomp +    end +  end + +  def create +    if resource[:match] +      handle_create_with_match +    elsif resource[:after] +      handle_create_with_after +    else +      append_line +    end +  end + +  def destroy +    local_lines = lines +    File.open(resource[:path],'w') do |fh| +      fh.write(local_lines.reject{|l| l.chomp == resource[:line] }.join('')) +    end +  end + +  private +  def lines +    # If this type is ever used with very large files, we should +    #  write this in a different way, using a temp +    #  file; for now assuming that this type is only used on +    #  small-ish config files that can fit into memory without +    #  too much trouble. +    @lines ||= File.readlines(resource[:path]) +  end + +  def handle_create_with_match() +    regex = resource[:match] ? Regexp.new(resource[:match]) : nil +    match_count = count_matches(regex) +    if match_count > 1 && resource[:multiple].to_s != 'true' +     raise Puppet::Error, "More than one line in file '#{resource[:path]}' matches pattern '#{resource[:match]}'" +    end +    File.open(resource[:path], 'w') do |fh| +      lines.each do |l| +        fh.puts(regex.match(l) ? resource[:line] : l) +      end + +      if (match_count == 0) +        fh.puts(resource[:line]) +      end +    end +  end + +  def handle_create_with_after +    regex = Regexp.new(resource[:after]) +    count = count_matches(regex) +    case count +    when 1 # find the line to put our line after +      File.open(resource[:path], 'w') do |fh| +        lines.each do |l| +          fh.puts(l) +          if regex.match(l) then +            fh.puts(resource[:line]) +          end +        end +      end +    when 0 # append the line to the end of the file +      append_line +    else +      raise Puppet::Error, "#{count} lines match pattern '#{resource[:after]}' in file '#{resource[:path]}'.  One or no line must match the pattern." +    end +  end + +  def count_matches(regex) +    lines.select{|l| l.match(regex)}.size +  end + +  ## +  # append the line to the file. +  # +  # @api private +  def append_line +    File.open(resource[:path], 'a') do |fh| +      fh.puts resource[:line] +    end +  end +end diff --git a/lib/puppet/type/anchor.rb b/lib/puppet/type/anchor.rb new file mode 100644 index 00000000..fe1e5aa1 --- /dev/null +++ b/lib/puppet/type/anchor.rb @@ -0,0 +1,46 @@ +Puppet::Type.newtype(:anchor) do +  desc <<-'ENDOFDESC' +  A simple resource type intended to be used as an anchor in a composite class. + +  In Puppet 2.6, when a class declares another class, the resources in the +  interior class are not contained by the exterior class. This interacts badly +  with the pattern of composing complex modules from smaller classes, as it +  makes it impossible for end users to specify order relationships between the +  exterior class and other modules. + +  The anchor type lets you work around this. By sandwiching any interior +  classes between two no-op resources that _are_ contained by the exterior +  class, you can ensure that all resources in the module are contained. + +      class ntp { +        # These classes will have the correct order relationship with each +        # other. However, without anchors, they won't have any order +        # relationship to Class['ntp']. +        class { 'ntp::package': } +        -> class { 'ntp::config': } +        -> class { 'ntp::service': } + +        # These two resources "anchor" the composed classes within the ntp +        # class. +        anchor { 'ntp::begin': } -> Class['ntp::package'] +        Class['ntp::service']    -> anchor { 'ntp::end': } +      } + +  This allows the end user of the ntp module to establish require and before +  relationships with Class['ntp']: + +      class { 'ntp': } -> class { 'mcollective': } +      class { 'mcollective': } -> class { 'ntp': } + +  ENDOFDESC + +  newparam :name do +    desc "The name of the anchor resource." +  end + +  def refresh +    # We don't do anything with them, but we need this to +    #   show that we are "refresh aware" and not break the +    #   chain of propagation. +  end +end diff --git a/lib/puppet/type/file_line.rb b/lib/puppet/type/file_line.rb new file mode 100644 index 00000000..df263e6a --- /dev/null +++ b/lib/puppet/type/file_line.rb @@ -0,0 +1,75 @@ +Puppet::Type.newtype(:file_line) do + +  desc <<-EOT +    Ensures that a given line is contained within a file.  The implementation +    matches the full line, including whitespace at the beginning and end.  If +    the line is not contained in the given file, Puppet will add the line to +    ensure the desired state.  Multiple resources may be declared to manage +    multiple lines in the same file. + +    Example: + +        file_line { 'sudo_rule': +          path => '/etc/sudoers', +          line => '%sudo ALL=(ALL) ALL', +        } +        file_line { 'sudo_rule_nopw': +          path => '/etc/sudoers', +          line => '%sudonopw ALL=(ALL) NOPASSWD: ALL', +        } + +    In this example, Puppet will ensure both of the specified lines are +    contained in the file /etc/sudoers. + +    **Autorequires:** If Puppet is managing the file that will contain the line +    being managed, the file_line resource will autorequire that file. + +  EOT + +  ensurable do +    defaultvalues +    defaultto :present +  end + +  newparam(:name, :namevar => true) do +    desc 'An arbitrary name used as the identity of the resource.' +  end + +  newparam(:match) do +    desc 'An optional regular expression to run against existing lines in the file;\n' + +        'if a match is found, we replace that line rather than adding a new line.' +  end + +  newparam(:multiple) do +    desc 'An optional value to determine if match can change multiple lines.' +    newvalues(true, false) +  end + +  newparam(:after) do +    desc 'An optional value used to specify the line after which we will add any new lines. (Existing lines are added in place)' +  end + +  newparam(:line) do +    desc 'The line to be appended to the file located by the path parameter.' +  end + +  newparam(:path) do +    desc 'The file Puppet will ensure contains the line specified by the line parameter.' +    validate do |value| +      unless (Puppet.features.posix? and value =~ /^\//) or (Puppet.features.microsoft_windows? and (value =~ /^.:\// or value =~ /^\/\/[^\/]+\/[^\/]+/)) +        raise(Puppet::Error, "File paths must be fully qualified, not '#{value}'") +      end +    end +  end + +  # Autorequire the file resource if it's being managed +  autorequire(:file) do +    self[:path] +  end + +  validate do +    unless self[:line] and self[:path] +      raise(Puppet::Error, "Both line and path are required attributes") +    end +  end +end | 
