From 7ef2f7b0d726f8657d3c370979c52c5bd6440ea1 Mon Sep 17 00:00:00 2001 From: Alex Schultz Date: Wed, 9 Nov 2016 13:40:47 -0700 Subject: [PATCH] os_transport_url parser function This change adds a os_transport_url function that can be used to generate correct URIs for the transport_url setting used by oslo.messaging. Change-Id: If83c0f0e61a08061334536399a42767a305966b7 --- .../parser/functions/os_transport_url.rb | 146 ++++++++++++ .../os_transport_url-b6fe15a8f21d387b.yaml | 4 + spec/functions/os_transport_url_spec.rb | 212 ++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 lib/puppet/parser/functions/os_transport_url.rb create mode 100644 releasenotes/notes/os_transport_url-b6fe15a8f21d387b.yaml create mode 100644 spec/functions/os_transport_url_spec.rb diff --git a/lib/puppet/parser/functions/os_transport_url.rb b/lib/puppet/parser/functions/os_transport_url.rb new file mode 100644 index 00000000..a19c7cc4 --- /dev/null +++ b/lib/puppet/parser/functions/os_transport_url.rb @@ -0,0 +1,146 @@ +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) 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| + 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 + + 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']}" 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']]) + hosts = hosts.map{ |h| "#{h}:#{v['port']}" } 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') + if v.include?('query') + v['query'].merge!({ 'ssl' => v['ssl'] }) + else + v['query'] = { 'ssl' => v['ssl'] } + 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 diff --git a/releasenotes/notes/os_transport_url-b6fe15a8f21d387b.yaml b/releasenotes/notes/os_transport_url-b6fe15a8f21d387b.yaml new file mode 100644 index 00000000..5b4f7f3f --- /dev/null +++ b/releasenotes/notes/os_transport_url-b6fe15a8f21d387b.yaml @@ -0,0 +1,4 @@ +--- +features: + - os_transport_url puppet parser function can be used to generate valid + transport_url URIs from a hash of connection parameters. diff --git a/spec/functions/os_transport_url_spec.rb b/spec/functions/os_transport_url_spec.rb new file mode 100644 index 00000000..888dfe53 --- /dev/null +++ b/spec/functions/os_transport_url_spec.rb @@ -0,0 +1,212 @@ +require 'spec_helper' + +describe 'os_transport_url' do + + it 'refuses String' do + is_expected.to run.with_params('foo').\ + and_raise_error(Puppet::ParseError, /Requires an hash/) + end + + it 'refuses Array' do + is_expected.to run.with_params(['foo']).\ + and_raise_error(Puppet::ParseError, /Requires an hash/) + end + + it 'refuses without at least one argument' do + is_expected.to run.with_params().\ + and_raise_error(ArgumentError, /Wrong number of arguments/) + end + + it 'refuses too many arguments' do + is_expected.to run.with_params('foo', 'bar').\ + and_raise_error(ArgumentError, /Wrong number of arguments/) + end + + it 'refuses hosts params passed as String' do + is_expected.to run.with_params({ + 'transport'=> 'rabbit', + 'hosts' => '127.0.0.1', + }).and_raise_error(Puppet::ParseError, /hosts should be a Array/) + end + + it 'fails if missing host' do + is_expected.to run.with_params({ + 'transport'=> 'rabbit', + }).and_raise_error(Puppet::ParseError, /host or hosts is required/) + end + + context 'creates the correct transport URI' do + + it 'with all params for a single host' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + 'port' => '5672', + 'username' => 'guest', + 'password' => 's3cr3t', + 'virtual_host' => 'virt', + 'ssl' => '1', + 'query' => { 'read_timeout' => '60' }, + }).and_return('rabbit://guest:s3cr3t@127.0.0.1:5672/virt?read_timeout=60&ssl=1') + end + + it 'with only required params for a single host' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + }).and_return('rabbit://127.0.0.1/') + end + + it 'with a single ipv6 address' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => 'fe80::ca5b:76ff:fe4b:be3b', + 'port' => '5672' + }).and_return('rabbit://[fe80::ca5b:76ff:fe4b:be3b]:5672/') + end + + it 'with all params with multiple hosts' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'hosts' => ['1.1.1.1', '2.2.2.2'], + 'port' => '5672', + 'username' => 'guest', + 'password' => 's3cr3t', + 'virtual_host' => 'virt', + 'query' => { 'read_timeout' => '60' }, + }).and_return('rabbit://guest:s3cr3t@1.1.1.1:5672,guest:s3cr3t@2.2.2.2:5672/virt?read_timeout=60') + end + + it 'with only required params for multiple hosts' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'hosts' => [ '1.1.1.1', '2.2.2.2' ], + 'port' => '5672', + 'username' => 'guest', + 'password' => 's3cr3t', + }).and_return('rabbit://guest:s3cr3t@1.1.1.1:5672,guest:s3cr3t@2.2.2.2:5672/') + end + + it 'with multiple ipv6 hosts' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'hosts' => [ 'fe80::ca5b:76ff:fe4b:be3b', 'fe80::ca5b:76ff:fe4b:be3c' ], + 'port' => '5672', + 'username' => 'guest', + 'password' => 's3cr3t', + }).and_return('rabbit://guest:s3cr3t@[fe80::ca5b:76ff:fe4b:be3b]:5672,guest:s3cr3t@[fe80::ca5b:76ff:fe4b:be3c]:5672/') + end + + it 'with a mix of ipv4 and ipv6 hosts' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'hosts' => [ 'fe80::ca5b:76ff:fe4b:be3b', '1.1.1.1' ], + 'port' => '5672', + 'username' => 'guest', + 'password' => 's3cr3t', + }).and_return('rabbit://guest:s3cr3t@[fe80::ca5b:76ff:fe4b:be3b]:5672,guest:s3cr3t@1.1.1.1:5672/') + end + + it 'without port' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + 'username' => 'guest', + 'password' => 's3cr3t', + 'virtual_host' => 'virt', + 'query' => { 'read_timeout' => '60' }, + }).and_return('rabbit://guest:s3cr3t@127.0.0.1/virt?read_timeout=60') + end + + it 'without port and query' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + 'username' => 'guest', + 'password' => 's3cr3t', + 'virtual_host' => 'virt', + }).and_return('rabbit://guest:s3cr3t@127.0.0.1/virt') + end + + it 'without username and password' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + 'port' => '5672', + 'virtual_host' => 'virt', + 'query' => { 'read_timeout' => '60' }, + }).and_return('rabbit://127.0.0.1:5672/virt?read_timeout=60') + end + + it 'with username set to undef' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + 'port' => '5672', + 'username' => :undef, + 'virtual_host' => 'virt', + 'query' => { 'read_timeout' => '60' }, + }).and_return('rabbit://127.0.0.1:5672/virt?read_timeout=60') + end + + it 'with username set to an empty string' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + 'port' => '5672', + 'username' => '', + 'virtual_host' => 'virt', + 'query' => { 'read_timeout' => '60' }, + }).and_return('rabbit://127.0.0.1:5672/virt?read_timeout=60') + end + + it 'without password' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + 'port' => '5672', + 'username' => 'guest', + 'virtual_host' => 'virt', + 'query' => { 'read_timeout' => '60' }, + }).and_return('rabbit://guest@127.0.0.1:5672/virt?read_timeout=60') + end + + it 'with password set to undef' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + 'port' => '5672', + 'username' => 'guest', + 'password' => :undef, + 'virtual_host' => 'virt', + 'query' => { 'read_timeout' => '60' }, + }).and_return('rabbit://guest@127.0.0.1:5672/virt?read_timeout=60') + end + + it 'with password set to an empty string' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + 'port' => '5672', + 'username' => 'guest', + 'password' => '', + 'virtual_host' => 'virt', + 'query' => { 'read_timeout' => '60' }, + }).and_return('rabbit://guest@127.0.0.1:5672/virt?read_timeout=60') + end + + it 'with ssl overrides ssl in querty hash' do + is_expected.to run.with_params({ + 'transport' => 'rabbit', + 'host' => '127.0.0.1', + 'port' => '5672', + 'username' => 'guest', + 'password' => '', + 'virtual_host' => 'virt', + 'ssl' => '1', + 'query' => { 'read_timeout' => '60' , 'ssl' => '0'}, + }).and_return('rabbit://guest@127.0.0.1:5672/virt?read_timeout=60&ssl=1') + end + + end +end