diff --git a/manifests/init.pp b/manifests/init.pp index 4ba5e3772..28c65d176 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -83,6 +83,8 @@ $worker_connections = $nginx::params::nx_worker_connections, $worker_processes = $nginx::params::nx_worker_processes, $worker_rlimit_nofile = $nginx::params::nx_worker_rlimit_nofile, + $geo_mappings = {}, + $string_mappings = {}, ) inherits nginx::params { include stdlib @@ -159,6 +161,9 @@ validate_string($proxy_headers_hash_bucket_size) validate_bool($super_user) + validate_hash($string_mappings) + validate_hash($geo_mappings) + class { 'nginx::package': package_name => $package_name, package_source => $package_source, @@ -221,6 +226,8 @@ create_resources('nginx::resource::vhost', $nginx_vhosts) create_resources('nginx::resource::location', $nginx_locations) create_resources('nginx::resource::mailhost', $nginx_mailhosts) + create_resources('nginx::resource::map', $string_mappings) + create_resources('nginx::resource::geo', $geo_mappings) # Allow the end user to establish relationships to the "main" class # and preserve the relationship to the implementation classes through diff --git a/manifests/resource/geo.pp b/manifests/resource/geo.pp new file mode 100644 index 000000000..6f91bb614 --- /dev/null +++ b/manifests/resource/geo.pp @@ -0,0 +1,90 @@ +# define: nginx::resource::geo +# +# This definition creates a new geo mapping entry for NGINX +# +# Parameters: +# [*networks*] - Hash of geo lookup keys and resultant values +# [*default*] - Sets the resulting value if the source value fails to +# match any of the variants. +# [*ensure*] - Enables or disables the specified location +# [*ranges*] - Indicates that lookup keys (network addresses) are +# specified as ranges. +# [*address*] - Nginx defaults to using $remote_addr for testing. +# This allows you to override that with another variable +# name (automatically prefixed with $) +# [*delete*] - deletes the specified network (see: geo module docs) +# [*proxy_recursive*] - Changes the behavior of address acquisition when +# specifying trusted proxies via 'proxies' directive +# [*proxies*] - Hash of network->value mappings. + +# Actions: +# +# Requires: +# +# Sample Usage: +# +# nginx::resource::geo { 'client_network': +# ensure => present, +# ranges => false, +# default => extra, +# proxy_recursive => false, +# proxies => [ '192.168.99.99' ], +# networks => { +# '10.0.0.0/8' => 'intra', +# '172.16.0.0/12' => 'intra', +# '192.168.0.0/16' => 'intra', +# } +# } +# +# Sample Hiera usage: +# +# nginx::geos: +# client_network: +# ensure: present +# ranges: false +# default: 'extra' +# proxy_recursive: false +# proxies: +# - 192.168.99.99 +# networks: +# '10.0.0.0/8': 'intra' +# '172.16.0.0/12': 'intra' +# '192.168.0.0/16': 'intra' + + +define nginx::resource::geo ( + $networks, + $default = undef, + $ensure = 'present', + $ranges = false, + $address = undef, + $delete = undef, + $proxies = undef, + $proxy_recursive = undef +) { + + validate_hash($networks) + validate_bool($ranges) + validate_re($ensure, '^(present|absent)$', + "Invalid ensure value '${ensure}'. Expected 'present' or 'absent'") + if ($default != undef) { validate_string($default) } + if ($address != undef) { validate_string($address) } + if ($delete != undef) { validate_string($delete) } + if ($proxies != undef) { validate_array($proxies) } + if ($proxy_recursive != undef) { validate_bool($proxy_recursive) } + + File { + owner => 'root', + group => 'root', + mode => '0644', + } + + file { "${nginx::params::nx_conf_dir}/conf.d/${name}-geo.conf": + ensure => $ensure ? { + 'absent' => absent, + default => 'file', + }, + content => template('nginx/conf.d/geo.erb'), + notify => Class['nginx::service'], + } +} diff --git a/manifests/resource/map.pp b/manifests/resource/map.pp new file mode 100644 index 000000000..a8066514b --- /dev/null +++ b/manifests/resource/map.pp @@ -0,0 +1,74 @@ +# define: nginx::resource::map +# +# This definition creates a new mapping entry for NGINX +# +# Parameters: +# [*ensure*] - Enables or disables the specified location (present|absent) +# [*default*] - Sets the resulting value if the source values fails to +# match any of the variants. +# [*string*] - Source string or variable to provide mapping for +# [*mappings*] - Hash of map lookup keys and resultant values +# [*hostnames*] - Indicates that source values can be hostnames with a +# prefix or suffix mask. + +# Actions: +# +# Requires: +# +# Sample Usage: +# +# nginx::resource::map { 'backend_pool': +# ensure => present, +# hostnames => true, +# default => 'ny-pool-1, +# string => '$http_host', +# mappings => { +# '*.nyc.example.com' => 'ny-pool-1', +# '*.sf.example.com' => 'sf-pool-1', +# } +# } +# +# Sample Hiera usage: +# +# nginx::maps: +# client_network: +# ensure: present +# hostnames: true +# default: 'ny-pool-1' +# string: $http_host +# mappings: +# '*.nyc.example.com': 'ny-pool-1' +# '*.sf.example.com': 'sf-pool-1' + + +define nginx::resource::map ( + $string, + $mappings, + $default = undef, + $ensure = 'present', + $hostnames = false +) { + validate_string($string) + validate_re($string, '^.{2,}$', + "Invalid string value [${string}]. Expected a minimum of 2 characters.") + validate_hash($mappings) + validate_bool($hostnames) + validate_re($ensure, '^(present|absent)$', + "Invalid ensure value '${ensure}'. Expected 'present' or 'absent'") + if ($default != undef) { validate_string($default) } + + File { + owner => 'root', + group => 'root', + mode => '0644', + } + + file { "${nginx::params::nx_conf_dir}/conf.d/${name}-map.conf": + ensure => $ensure ? { + 'absent' => absent, + default => 'file', + }, + content => template('nginx/conf.d/map.erb'), + notify => Class['nginx::service'], + } +} diff --git a/manifests/resource/vhost.pp b/manifests/resource/vhost.pp index a69040794..5b9bc17e6 100644 --- a/manifests/resource/vhost.pp +++ b/manifests/resource/vhost.pp @@ -194,6 +194,8 @@ $log_by_lua_file = undef, $use_default_location = true, $rewrite_rules = [], + $string_mappings = {}, + $geo_mappings = {}, ) { validate_re($ensure, '^(present|absent)$', @@ -332,6 +334,8 @@ } validate_bool($use_default_location) validate_array($rewrite_rules) + validate_hash($string_mappings) + validate_hash($geo_mappings) # Variables $vhost_dir = "${nginx::config::conf_dir}/sites-available" @@ -555,4 +559,7 @@ require => Concat[$config_file], notify => Service['nginx'], } + + create_resources('nginx::resource::map', $string_mappings) + create_resources('nginx::resource::geo', $geo_mappings) } diff --git a/spec/defines/resource_geo_spec.rb b/spec/defines/resource_geo_spec.rb new file mode 100644 index 000000000..f7aa3ee44 --- /dev/null +++ b/spec/defines/resource_geo_spec.rb @@ -0,0 +1,128 @@ +require 'spec_helper' + +describe 'nginx::resource::geo' do + let :title do + 'client_network' + end + + let :default_params do + { + :default => 'extra', + :networks => { + '172.16.0.0/12' => 'intra', + '192.168.0.0/16' => 'intra', + '10.0.0.0/8' => 'intra', + }, + :proxies => [ '1.2.3.4', '4.3.2.1' ] + } + end + + let :facts do + { + :osfamily => 'RedHat', + :operatingsystem => 'CentOS', + } + end + + let :pre_condition do + [ + 'include ::nginx::params', + ] + end + + describe 'os-independent items' do + describe 'basic assumptions' do + let :params do default_params end + + it { should contain_file("/etc/nginx/conf.d/#{title}-geo.conf").with( + { + 'owner' => 'root', + 'group' => 'root', + 'mode' => '0644', + 'ensure' => 'file', + 'content' => /geo \$#{title}/, + } + )} + end + + describe "geo.conf template content" do + [ + { + :title => 'should set address', + :attr => 'address', + :value => '$remote_addr', + :match => 'geo $remote_addr $client_network {' + }, + { + :title => 'should set ranges', + :attr => 'ranges', + :value => true, + :match => ' ranges;' + }, + { + :title => 'should set default', + :attr => 'default', + :value => 'extra', + :match => [ ' default extra;' ], + }, + { + :title => 'should contain ordered network directives', + :attr => 'networks', + :value => { + '192.168.0.0/16' => 'intra', + '172.16.0.0/12' => 'intra', + '10.0.0.0/8' => 'intra', + }, + :match => [ + ' 10.0.0.0/8 intra;', + ' 172.16.0.0/12 intra;', + ' 192.168.0.0/16 intra;', + ], + }, + { + :title => 'should set multiple proxies', + :attr => 'proxies', + :value => [ '1.2.3.4', '4.3.2.1' ], + :match => [ + ' proxy 1.2.3.4;', + ' proxy 4.3.2.1;' + ] + }, + { + :title => 'should set proxy_recursive', + :attr => 'proxy_recursive', + :value => true, + :match => ' proxy_recursive;' + }, + { + :title => 'should set delete', + :attr => 'delete', + :value => '192.168.0.0/16', + :match => ' delete 192.168.0.0/16;' + }, + ].each do |param| + context "when #{param[:attr]} is #{param[:value]}" do + let :params do default_params.merge({ param[:attr].to_sym => param[:value] }) end + + it { should contain_file("/etc/nginx/conf.d/#{title}-geo.conf").with_mode('0644') } + it param[:title] do + verify_contents(subject, "/etc/nginx/conf.d/#{title}-geo.conf", Array(param[:match])) + Array(param[:notmatch]).each do |item| + should contain_file("/etc/nginx/conf.d/#{title}-geo.conf").without_content(item) + end + end + end + end + + context 'when ensure => absent' do + let :params do default_params.merge( + { + :ensure => 'absent' + } + ) end + + it { should contain_file("/etc/nginx/conf.d/#{title}-geo.conf").with_ensure('absent') } + end + end + end +end diff --git a/spec/defines/resource_map_spec.rb b/spec/defines/resource_map_spec.rb new file mode 100644 index 000000000..4e843812a --- /dev/null +++ b/spec/defines/resource_map_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe 'nginx::resource::map' do + let :title do + 'backend_pool' + end + + let :default_params do + { + :string => '$uri', + :default => 'pool_a', + :mappings => { + 'foo' => 'pool_b', + 'bar' => 'pool_c', + 'baz' => 'pool_d', + }, + } + end + + let :facts do + { + :osfamily => 'RedHat', + :operatingsystem => 'CentOS', + } + end + + let :pre_condition do + [ + 'include ::nginx::params', + ] + end + + describe 'os-independent items' do + describe 'basic assumptions' do + let :params do default_params end + + it { should contain_file("/etc/nginx/conf.d/#{title}-map.conf").with( + { + 'owner' => 'root', + 'group' => 'root', + 'mode' => '0644', + 'ensure' => 'file', + 'content' => /map \$uri \$#{title}/, + } + )} + end + + describe "map.conf template content" do + [ + { + :title => 'should set hostnames', + :attr => 'hostnames', + :value => true, + :match => ' hostnames;' + }, + { + :title => 'should set default', + :attr => 'default', + :value => 'pool_a', + :match => [ ' default pool_a;' ], + }, + { + :title => 'should contain ordered mappings', + :attr => 'mappings', + :value => { + 'foo' => 'pool_b', + 'bar' => 'pool_c', + 'baz' => 'pool_d', + }, + :match => [ + ' bar pool_c;', + ' baz pool_d;', + ' foo pool_b;', + ], + }, + ].each do |param| + context "when #{param[:attr]} is #{param[:value]}" do + let :params do default_params.merge({ param[:attr].to_sym => param[:value] }) end + + it { should contain_file("/etc/nginx/conf.d/#{title}-map.conf").with_mode('0644') } + it param[:title] do + verify_contents(subject, "/etc/nginx/conf.d/#{title}-map.conf", Array(param[:match])) + Array(param[:notmatch]).each do |item| + should contain_file("/etc/nginx/conf.d/#{title}-map.conf").without_content(item) + end + end + end + end + + context 'when ensure => absent' do + let :params do default_params.merge( + { + :ensure => 'absent' + } + ) end + + it { should contain_file("/etc/nginx/conf.d/#{title}-map.conf").with_ensure('absent') } + end + end + end +end diff --git a/templates/conf.d/geo.erb b/templates/conf.d/geo.erb new file mode 100644 index 000000000..0b6da7d43 --- /dev/null +++ b/templates/conf.d/geo.erb @@ -0,0 +1,29 @@ +<% + # sorting ip addresses in ascending order is more efficient for nginx - so we need + # to convert them to numbers first via IPAddr + require 'ipaddr' +-%> +geo <%= @address ? "#{@address} " : '' %>$<%= @name %> { +<% if @ranges -%> + ranges; +<% end -%> +<% if @default -%> + default <%= @default %>; +<% end -%> +<% if @delete -%> + delete <%= @delete %>; +<% end -%> +<% if @proxies -%> + <%- [@proxies].flatten.each do |proxy| -%> + proxy <%= proxy %>; + <%- end -%> +<% end -%> +<% if @proxy_recursive && @proxies -%> + proxy_recursive; +<% end -%> +<% if @networks -%> + <%- @networks.sort_by{|k,v| IPAddr.new(k.split('-').first).to_i }.each do |key,value| -%> + <%= key %> <%= value %>; + <%- end -%> +<% end -%> +} diff --git a/templates/conf.d/map.erb b/templates/conf.d/map.erb new file mode 100644 index 000000000..67fd5c52b --- /dev/null +++ b/templates/conf.d/map.erb @@ -0,0 +1,13 @@ +map <%= @string %> $<%= @name %> { +<% if @hostnames -%> + hostnames; +<% end -%> +<% if @default -%> + default <%= @default %>; +<% end -%> +<% if @mappings -%> + <%- @mappings.sort_by{|k,v| k}.each do |key,value| -%> + <%= key %> <%= value %>; + <%- end -%> +<% end -%> +}