From fd3156bc60ec537ffc53aea1d0e0035c9fe17638 Mon Sep 17 00:00:00 2001 From: Tobias Urdin Date: Tue, 30 Oct 2018 15:38:19 +0100 Subject: [PATCH] Use puppet4 functions-api Change-Id: I07a6ad9dba09c664e0b95901a442b165a29a8852 --- .../functions/is_service_default.rb | 15 +- lib/puppet/functions/normalize_ip_for_uri.rb | 36 ++++ .../functions/os_database_connection.rb | 75 +++++++ lib/puppet/functions/os_transport_url.rb | 189 ++++++++++++++++++ .../parser/functions/normalize_ip_for_uri.rb | 37 ---- .../functions/os_database_connection.rb | 81 -------- .../parser/functions/os_transport_url.rb | 157 --------------- 7 files changed, 304 insertions(+), 286 deletions(-) rename lib/puppet/{parser => }/functions/is_service_default.rb (60%) create mode 100644 lib/puppet/functions/normalize_ip_for_uri.rb create mode 100644 lib/puppet/functions/os_database_connection.rb create mode 100644 lib/puppet/functions/os_transport_url.rb delete mode 100644 lib/puppet/parser/functions/normalize_ip_for_uri.rb delete mode 100644 lib/puppet/parser/functions/os_database_connection.rb delete mode 100644 lib/puppet/parser/functions/os_transport_url.rb diff --git a/lib/puppet/parser/functions/is_service_default.rb b/lib/puppet/functions/is_service_default.rb similarity index 60% rename from lib/puppet/parser/functions/is_service_default.rb rename to lib/puppet/functions/is_service_default.rb index 9af9148b..a016a603 100644 --- a/lib/puppet/parser/functions/is_service_default.rb +++ b/lib/puppet/functions/is_service_default.rb @@ -1,22 +1,15 @@ -# -# is_service_default.rb -# # This function can be used to check if a variable is set to the default value # of '' # # For reference: # http://lists.openstack.org/pipermail/openstack-dev/2015-July/069823.html # https://github.com/openstack/puppet-openstacklib/commit/3b85306d042292713d0fd89fa508e0a0fbf99671 -# -module Puppet::Parser::Functions - newfunction(:is_service_default, :type => :rvalue, :doc => <<-EOS -Returns true if the variable passed to this function is '' - EOS - ) do |arguments| +Puppet::Functions.create_function(:is_service_default) do + def is_service_default(*args) raise(Puppet::ParseError, "is_service_default(): Wrong number of arguments" + - "given (#{arguments.size} for 1)") if arguments.size != 1 + "given (#{args.size} for 1)") if args.size != 1 - value = arguments[0] + value = args[0] unless value.is_a?(String) return false diff --git a/lib/puppet/functions/normalize_ip_for_uri.rb b/lib/puppet/functions/normalize_ip_for_uri.rb new file mode 100644 index 00000000..3713b078 --- /dev/null +++ b/lib/puppet/functions/normalize_ip_for_uri.rb @@ -0,0 +1,36 @@ +# Add brackets if the argument is an IPv6 address. +# Returns the argument untouched otherwise. +# +# CAUTION: this code "fails" when the user is passing +# an IPv6 address with the port in it without the +# brackets: 2001::1:8080, to specify address 2001::1 +# and port 8080. This code will change it to +# [2001::1:8080] as it's a valid ip address. This +# shouldn't be an issue in most cases. +# +# If an array is given, each member will be normalized to +# a valid IPv6 address with brackets when needed. +Puppet::Functions.create_function(:normalize_ip_for_uri) do + def normalize_ip_for_uri(*args) + require 'ipaddr' + + result = [] + args = args[0] if args[0].kind_of?(Array) + args = [args] unless args.kind_of?(Array) + args.each do |ip| + begin + if IPAddr.new(ip).ipv6? + unless ip.match(/\[.+\]/) + Puppet.debug("IP #{ip} is changed to [#{ip}]") + ip = "[#{ip}]" + end + end + rescue ArgumentError + # ignore it + end + result << ip + end + return result[0] if args.size == 1 + result + end +end diff --git a/lib/puppet/functions/os_database_connection.rb b/lib/puppet/functions/os_database_connection.rb new file mode 100644 index 00000000..f63c70b1 --- /dev/null +++ b/lib/puppet/functions/os_database_connection.rb @@ -0,0 +1,75 @@ +Puppet::Functions.create_function(:os_database_connection) do + def os_database_connection(*args) + require 'uri' + + if (args.size != 1) then + raise(Puppet::ParseError, "os_database_connection(): Wrong number of arguments " + + "given (#{args.size} for 1)") + end + + v = args[0] + klass = v.class + + unless klass == Hash + raise(Puppet::ParseError, "os_database_connection(): Requires an hash, got #{klass}") + end + + v.keys.each do |key| + klass = (key == 'extra') ? Hash : String + unless (v[key].class == klass) or (v[key] == :undef) + raise(Puppet::ParseError, "os_database_connection(): #{key} should be a #{klass}") + end + end + + parts = {} + + unless v.include?('dialect') + raise(Puppet::ParseError, 'os_database_connection(): dialect is required') + end + + if v.include?('host') + parts[:host] = v['host'] + end + + unless v.include?('database') + raise(Puppet::ParseError, 'os_database_connection(): database is required') + end + + if v.include?('port') + if v.include?('host') + parts[:port] = v['port'].to_i + else + raise(Puppet::ParseError, 'os_database_connection(): host is required with port') + end + end + + if v.include?('username') and (v['username'] != :undef) and (v['username'].to_s != '') + parts[:userinfo] = URI.escape(v['username']) + if v.include?('password') and (v['password'] != :undef) and (v['password'].to_s != '') + parts[:userinfo] += ":#{URI.escape(v['password'])}" + end + end + + # support previous charset option on the function. Setting charset will + # override charset if passed in via the extra parameters + if v.include?('charset') + if v.include?('extra') + v['extra'].merge!({ 'charset' => v['charset'] }) + else + v['extra'] = { 'charset' => v['charset'] } + end + end + + parts[:query] = v['extra'].map{ |k,v| "#{k}=#{v}" }.join('&') if v.include?('extra') + + parts[:scheme] = v['dialect'] + + if v.include?('host') + parts[:path] = "/#{v['database']}" + else + parts[:path] = "///#{v['database']}" + end + + URI::Generic.build(parts).to_s + end +end diff --git a/lib/puppet/functions/os_transport_url.rb b/lib/puppet/functions/os_transport_url.rb new file mode 100644 index 00000000..b8f90b03 --- /dev/null +++ b/lib/puppet/functions/os_transport_url.rb @@ -0,0 +1,189 @@ +# This function builds a os_transport_url string from a hash of parameters. +# +# Valid hash parameteres: +# * transport - (string) type of transport, 'rabbit' or 'amqp' +# * host - (string) single host +# * hosts - (array) array of hosts to use +# * port - (string | integer) port to connect to +# * username - (string) connection username +# * password - (string) connection password +# * virtual_host - (string) virtual host to connect to +# * ssl - (string) is the connection ssl or not ('1' or '0'). overrides the ssl +# key in the query parameter +# * query - (hash) hash of key,value pairs used to create a query string for +# the transport_url. +# +# Only 'transport' and either 'host' or 'hosts' are required keys for the +# parameters hash. +# +# The url format that will be generated: +# transport://user:pass@host:port[,userN:passN@hostN:portN]/virtual_host?query +# +# NOTE: ipv6 addresses will automatically be bracketed for the URI using the +# normalize_ip_for_uri function. +# +# Single Host Example: +# os_transport_url({ +# 'transport' => 'rabbit', +# 'host' => '1.1.1.1', +# 'port' => '5672', +# 'username' => 'username', +# 'password' => 'password', +# 'virtual_host' => 'virtual_host', +# 'ssl' => '1', +# 'query' => { 'key' => 'value' }, +# }) +# +# Generates: +# rabbit://username:password@1.1.1.1:5672/virtual_host?key=value&ssl=1 +# +# Multiple Hosts Example: +# os_transport_url({ +# 'transport' => 'rabbit', +# 'hosts' => [ '1.1.1.1', '2.2.2.2' ], +# 'port' => '5672', +# 'username' => 'username', +# 'password' => 'password', +# 'virtual_host' => 'virtual_host', +# 'query' => { 'key' => 'value' }, +# }) +# +# Generates: +# rabbit://username:password@1.1.1.1:5672,username:password@2.2.2.2:5672/virtual_host?key=value +Puppet::Functions.create_function(:os_transport_url) do + # TODO(tobias-urdin): Rework and remove this. + # Taken straight from stdlib v5.1.0 module. + def _str2bool(string) + if !!string == string + return string + end + unless string.is_a?(String) + raise(Puppet::ParseError, 'str2bool(): Requires string to work with') + end + result = case string + when %r{^$}, '' then false # Empty string will be false ... + when %r{^(1|t|y|true|yes)$}i then true + when %r{^(0|f|n|false|no)$}i then false + when %r{^(undef|undefined)$} then false # This is not likely to happen ... + else + raise(Puppet::ParseError, 'os_transport_url _str2bool(): Unknown type of boolean given') + end + return result + end + + # TODO(tobias-urdin): Rework and remove this. + # Taken straight from stdlib v5.1.0 module. + def _bool2num(val) + value = _str2bool(val) + result = value ? 1 : 0 + return result + end + + def os_transport_url(*args) + require 'uri' + + unless args.size == 1 + raise(ArgumentError, 'os_transport_url(): Wrong number of arguments') + end + + v = args[0] + klass = v.class + + unless klass == Hash + raise(Puppet::ParseError, "os_transport_url(): Requires an hash, got #{klass}") + end + + # type checking for the parameter hash + v.keys.each do |key| + v[key] = v[key].to_s if key == 'port' + klass = (key == 'hosts') ? Array : String + klass = (key == 'query') ? Hash : klass + unless (v[key].class == klass) or (v[key] == :undef) + raise(Puppet::ParseError, "os_transport_url(): #{key} should be a #{klass}") + end + end + + # defaults + parts = { + :transport => 'rabbit', + :hostinfo => 'localhost', + :path => '/', + } + + unless v.include?('transport') + raise(Puppet::ParseError, 'os_transport_url(): transport is required') + end + + unless v.include?('host') or v.include?('hosts') + raise(Puppet::ParseError, 'os_transport_url(): host or hosts is required') + end + + if v.include?('host') and v.include?('hosts') + raise(Puppet::ParseError, 'os_transport_url(): cannot use both host and hosts.') + end + + parts[:transport] = v['transport'] + + if v.include?('username') and (v['username'] != :undef) and (v['username'].to_s != '') + parts[:userinfo] = URI.escape(v['username']) + if v.include?('password') and (v['password'] != :undef) and (v['password'].to_s != '') + parts[:userinfo] += ":#{URI.escape(v['password'])}" + end + end + + if v.include?('host') + host = call_function('normalize_ip_for_uri', v['host']) + host += ":#{v['port'].to_s}" if v.include?('port') + if parts.include?(:userinfo) + parts[:hostinfo] = "#{parts[:userinfo]}@#{host}" + else + parts[:hostinfo] = "#{host}" + end + end + + if v.include?('hosts') + hosts = call_function('normalize_ip_for_uri', v['hosts']) + # normalize_ip_for_uri may return a string, so check that we still have an + # array + hosts = [hosts] if hosts.kind_of?(String) + hosts = hosts.map{ |h| "#{h}:#{v['port'].to_s}" } if v.include?('port') + if parts.include?(:userinfo) + parts[:hostinfo] = hosts.map { |h| "#{parts[:userinfo]}@#{h}" }.join(',') + else + parts[:hostinfo] = hosts.join(',') + end + end + + parts[:path] = "/#{v['virtual_host']}" if v.include?('virtual_host') + + # support previous ssl option on the function. Setting ssl will + # override ssl if passed in via the query parameters + if v.include?('ssl') + # ssl can be passed in as a query paramter but should be 0/1. See + # http://docs.celeryproject.org/projects/kombu/en/latest/userguide/connections.html#urls + # so we rely on _str2bool and _bool2num to ensure it's in the + # format + # TODO(tobias-urdin): Rework this to using proper data types and not the + # legacy puppet functions that is copied into this function statement. + # We need to do this right now because it fails testing if we call the + # str2bool or bool2num legacy functions using call_function. + ssl_str = _str2bool(v['ssl']) + ssl_val = _bool2num(v['ssl']) + if v.include?('query') + v['query'].merge!({ 'ssl' => ssl_val }) + else + v['query'] = { 'ssl' => ssl_val } + end + end + + parts[:query] = v['query'].map{ |k,val| "#{k}=#{val}" }.join('&') if v.include?('query') + + url_parts = [] + url_parts << parts[:transport] + url_parts << '://' + url_parts << parts[:hostinfo] + url_parts << parts[:path] + url_parts << '?' << parts[:query] if parts.include?(:query) + url_parts.join() + end +end diff --git a/lib/puppet/parser/functions/normalize_ip_for_uri.rb b/lib/puppet/parser/functions/normalize_ip_for_uri.rb deleted file mode 100644 index 92dead9a..00000000 --- a/lib/puppet/parser/functions/normalize_ip_for_uri.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'ipaddr' - -module Puppet::Parser::Functions - newfunction(:normalize_ip_for_uri, - :type => :rvalue, - :doc => <<-EOD - Add brackets if the argument is an IPv6 address. - Returns the argument untouched otherwise. - CAUTION: this code "fails" when the user is passing - an IPv6 address with the port in it without the - brackets: 2001::1:8080, to specify address 2001::1 - and port 8080. This code will change it to - [2001::1:8080] as it's a valid ip address. This - shouldn't be an issue in most cases. - If an array is given, each member will be normalized to - a valid IPv6 address with brackets when needed. - EOD - ) do |args| - result = [] - args = args[0] if args[0].kind_of?(Array) - args.each do |ip| - begin - if IPAddr.new(ip).ipv6? - unless ip.match(/\[.+\]/) - Puppet.debug("IP #{ip} is changed to [#{ip}]") - ip = "[#{ip}]" - end - end - rescue ArgumentError - # ignore it - end - result << ip - end - return result[0] if args.size == 1 - result - end -end diff --git a/lib/puppet/parser/functions/os_database_connection.rb b/lib/puppet/parser/functions/os_database_connection.rb deleted file mode 100644 index 4cc428d9..00000000 --- a/lib/puppet/parser/functions/os_database_connection.rb +++ /dev/null @@ -1,81 +0,0 @@ -require 'puppet/parser/functions' - -Puppet::Parser::Functions.newfunction(:os_database_connection, - :type => :rvalue, - :doc => <<-EOS -This function builds a os_database_connection string from various parameters. -EOS -) do |arguments| - - require 'uri' - - if (arguments.size != 1) then - raise(Puppet::ParseError, "os_database_connection(): Wrong number of arguments " + - "given (#{arguments.size} for 1)") - end - - v = arguments[0] - klass = v.class - - unless klass == Hash - raise(Puppet::ParseError, "os_database_connection(): Requires an hash, got #{klass}") - end - - v.keys.each do |key| - klass = (key == 'extra') ? Hash : String - unless (v[key].class == klass) or (v[key] == :undef) - raise(Puppet::ParseError, "os_database_connection(): #{key} should be a #{klass}") - end - end - - parts = {} - - unless v.include?('dialect') - raise(Puppet::ParseError, 'os_database_connection(): dialect is required') - end - - if v.include?('host') - parts[:host] = v['host'] - end - - unless v.include?('database') - raise(Puppet::ParseError, 'os_database_connection(): database is required') - end - - if v.include?('port') - if v.include?('host') - parts[:port] = v['port'].to_i - else - raise(Puppet::ParseError, 'os_database_connection(): host is required with port') - end - end - - if v.include?('username') and (v['username'] != :undef) and (v['username'].to_s != '') - parts[:userinfo] = URI.escape(v['username']) - if v.include?('password') and (v['password'] != :undef) and (v['password'].to_s != '') - parts[:userinfo] += ":#{URI.escape(v['password'])}" - end - end - - # support previous charset option on the function. Setting charset will - # override charset if passed in via the extra parameters - if v.include?('charset') - if v.include?('extra') - v['extra'].merge!({ 'charset' => v['charset'] }) - else - v['extra'] = { 'charset' => v['charset'] } - end - end - - parts[:query] = v['extra'].map{ |k,v| "#{k}=#{v}" }.join('&') if v.include?('extra') - - parts[:scheme] = v['dialect'] - - if v.include?('host') - parts[:path] = "/#{v['database']}" - else - parts[:path] = "///#{v['database']}" - end - - URI::Generic.build(parts).to_s -end diff --git a/lib/puppet/parser/functions/os_transport_url.rb b/lib/puppet/parser/functions/os_transport_url.rb deleted file mode 100644 index 7c611a91..00000000 --- a/lib/puppet/parser/functions/os_transport_url.rb +++ /dev/null @@ -1,157 +0,0 @@ -require 'puppet/parser/functions' - -Puppet::Parser::Functions.newfunction(:os_transport_url, - :type => :rvalue, - :arity => 1, - :doc => <<-EOS -This function builds a os_transport_url string from a hash of parameters. - -Valid hash parameteres: - * transport - (string) type of transport, 'rabbit' or 'amqp' - * host - (string) single host - * hosts - (array) array of hosts to use - * port - (string | integer) port to connect to - * username - (string) connection username - * password - (string) connection password - * virtual_host - (string) virtual host to connect to - * ssl - (string) is the connection ssl or not ('1' or '0'). overrides the ssl - key in the query parameter - * query - (hash) hash of key,value pairs used to create a query string for - the transport_url. - -Only 'transport' and either 'host' or 'hosts' are required keys for the -parameters hash. - -The url format that will be generated: -transport://user:pass@host:port[,userN:passN@hostN:portN]/virtual_host?query - -NOTE: ipv6 addresses will automatically be bracketed for the URI using the -normalize_ip_for_uri function. - -Single Host Example: -os_transport_url({ - 'transport' => 'rabbit', - 'host' => '1.1.1.1', - 'port' => '5672', - 'username' => 'username', - 'password' => 'password', - 'virtual_host' => 'virtual_host', - 'ssl' => '1', - 'query' => { 'key' => 'value' }, -}) -Generates: -rabbit://username:password@1.1.1.1:5672/virtual_host?key=value&ssl=1 - -Multiple Hosts Example: -os_transport_url({ - 'transport' => 'rabbit', - 'hosts' => [ '1.1.1.1', '2.2.2.2' ], - 'port' => '5672', - 'username' => 'username', - 'password' => 'password', - 'virtual_host' => 'virtual_host', - 'query' => { 'key' => 'value' }, -}) -Generates: -rabbit://username:password@1.1.1.1:5672,username:password@2.2.2.2:5672/virtual_host?key=value -EOS -) do |arguments| - - require 'uri' - - v = arguments[0] - klass = v.class - - unless klass == Hash - raise(Puppet::ParseError, "os_transport_url(): Requires an hash, got #{klass}") - end - - # type checking for the parameter hash - v.keys.each do |key| - v[key] = v[key].to_s if key == 'port' - klass = (key == 'hosts') ? Array : String - klass = (key == 'query') ? Hash : klass - unless (v[key].class == klass) or (v[key] == :undef) - raise(Puppet::ParseError, "os_transport_url(): #{key} should be a #{klass}") - end - end - - # defaults - parts = { - :transport => 'rabbit', - :hostinfo => 'localhost', - :path => '/', - } - - unless v.include?('transport') - raise(Puppet::ParseError, 'os_transport_url(): transport is required') - end - - unless v.include?('host') or v.include?('hosts') - raise(Puppet::ParseError, 'os_transport_url(): host or hosts is required') - end - - if v.include?('host') and v.include?('hosts') - raise(Puppet::ParseError, 'os_transport_url(): cannot use both host and hosts.') - end - - parts[:transport] = v['transport'] - - if v.include?('username') and (v['username'] != :undef) and (v['username'].to_s != '') - parts[:userinfo] = URI.escape(v['username']) - if v.include?('password') and (v['password'] != :undef) and (v['password'].to_s != '') - parts[:userinfo] += ":#{URI.escape(v['password'])}" - end - end - - if v.include?('host') - host = function_normalize_ip_for_uri([v['host']]) - host += ":#{v['port'].to_s}" if v.include?('port') - if parts.include?(:userinfo) - parts[:hostinfo] = "#{parts[:userinfo]}@#{host}" - else - parts[:hostinfo] = "#{host}" - end - end - - if v.include?('hosts') - hosts = function_normalize_ip_for_uri([v['hosts']]) - # normalize_ip_for_uri may return a string, so check that we still have an - # array - hosts = [hosts] if hosts.kind_of?(String) - hosts = hosts.map{ |h| "#{h}:#{v['port'].to_s}" } if v.include?('port') - if parts.include?(:userinfo) - parts[:hostinfo] = hosts.map { |h| "#{parts[:userinfo]}@#{h}" }.join(',') - else - parts[:hostinfo] = hosts.join(',') - end - end - - parts[:path] = "/#{v['virtual_host']}" if v.include?('virtual_host') - - # support previous ssl option on the function. Setting ssl will - # override ssl if passed in via the query parameters - if v.include?('ssl') - # ssl can be passed in as a query paramter but should be 0/1. See - # http://docs.celeryproject.org/projects/kombu/en/latest/userguide/connections.html#urls - # so we rely on the stdlib str2bool and bool2num to ensure it's in the - # format - ssl_val = function_bool2num([function_str2bool([v['ssl']])]) - if v.include?('query') - v['query'].merge!({ 'ssl' => ssl_val }) - else - v['query'] = { 'ssl' => ssl_val } - end - end - - parts[:query] = v['query'].map{ |k,val| "#{k}=#{val}" }.join('&') if v.include?('query') - - - url_parts = [] - url_parts << parts[:transport] - url_parts << '://' - url_parts << parts[:hostinfo] - url_parts << parts[:path] - url_parts << '?' << parts[:query] if parts.include?(:query) - url_parts.join() -end