2

Linux servers in my company are managed by Puppet.

There's a DNS module which configures /etc/resolv.conf on all servers based on physical location which is configured as a facter value.

As you know a /etc/resolv.conf file looks like so:

search domain.local
nameserver 1.1.1.1
nameserver 2.2.2.2

All servers's host names in the company end with two digits, for example:

proxy73

In order to split DNS network traffic between the two DNS servers I've written a new puppet module which cuts the last two digits of the host name and if it's an odd number then the /etc/resolv.conf file should look like shown above, but if the digits create an uneven number then the /etc/resolv.conf file should look like so:

search domain.local
nameserver 2.2.2.2
nameserver 1.1.1.1

But my problem is that no matter how I write the manifest, the lines are always ordered as the first server and then the second instead of second server and then first server.

The relevant portion of the manifest I wrote looks like that (please refer to the part under if $::oddip == false cause that's the part that doesn't work):

class dns_new::config {
  case $::dcd {
 'ny4': {
      if $::oddip == 'true' {
        file_line { "ny4 search domain":
          ensure => present,
          line   => "${::dns_new::params::searchdomny4}",
          path   => "/etc/resolv.conf",
          }
        file_line { "ny4dns1 first":
          ensure => present,
          line   => "${::dns_new::params::ny4dns1}",
          path   => "/etc/resolv.conf",
          }
        file_line { "ny4dns2 second":
          ensure => present,
          line   => "${::dns_new::params::ny4dns2}",
          path   => "/etc/resolv.conf",
          }
      }
      elsif $::oddip == 'false'  {
        file_line { "ny4 search domain":
          ensure => present,
          line   => "${::dns_new::params::searchdomny4}",
          path   => "/etc/resolv.conf",
          }
        file_line { "ny4dns2 first":
          ensure => present,
          line   => "${::dns_new::params::ny4dns2}",
          path   => "/etc/resolv.conf",
          require => File_line["ny4 search domain"],
          before => File_line["ny4dns1 second"],
          }
        file_line { "ny4dns1 second":
          ensure => present,
          line   => "${::dns_new::params::ny4dns1}",
          path   => "/etc/resolv.conf",
          require => File_line["ny4dns2 first"],
          }
      }
    }

You can see that I've tried setting the order using the before directive.

This is all in regard to setting a new server, but how can I set the order of lines for an already installed server?

Edit #2:

sysadmin1183, I've added the "do" as you showed, now the error is this:

[root@nyproxy33 ~]# puppet agent -t
Info: Retrieving plugin
Info: Loading facts
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: compile error
/etc/puppet/environments/production/modules/dns_new/templates/resolv.conf.erb:27: syntax error, unexpected $end, expecting kEND
; _erbout
         ^
Warning: Not using cache on failed catalog
Error: Could not retrieve catalog; skipping run

The 27th line is:

<% end %>

Tried to edit it to:

<% end -%>

But getting the same output...

Edit #3: config.pp looks like so:

class dns_new::config {
  file { "/etc/resolv.conf":
      path    => '/etc/resolv.conf',
      ensure  => present,
      owner   => "root",
      group   => "root",
      mode    => "775",
      content => template("dns_new/resolv.conf.erb"),
      }

  case $::dcd {
    'ny4': {
      $search_dom = $::dns_new::params::searchdomny4
      if $::oddip == 'true' {
        $dns_list = [ "${::dns_new::params::ny4dns1}", "${::dns_new::params::ny4dns2}" ]
        }
      elsif $::oddip == 'false' {
        $dns_list = [ "${::dns_new::params::ny4dns2}", "${::dns_new::params::ny4dns1}" ]
        }
    }

The resolv.conf.erb file looks like this:

search <%= @search_dom %>
<% dns_list.each do |serv| -%>
nameserver <%= serv %>
<% end -%>

Running Puppet:

[root@nyproxy33 ~]# puppet agent -t
Info: Retrieving plugin
Info: Loading facts
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Failed to parse template dns_new/resolv.conf.erb:
  Filepath: /usr/lib/ruby/site_ruby/1.8/puppet/parser/templatewrapper.rb
  Line: 81
  Detail: Could not find value for 'dns_list' at /etc/puppet/environments/production/modules/dns_new/templates/resolv.conf.erb:2
 at /etc/puppet/environments/production/modules/dns_new/manifests/config.pp:8 on node nyproxy33.ny4.peer39.com
Warning: Not using cache on failed catalog
Error: Could not retrieve catalog; skipping run
1
  • 2
    I understand that you're trying to load balance, but have you considered the rotate option in resolv.conf as well?
    – ewwhite
    Aug 30, 2015 at 12:58

2 Answers 2

5

You may be better served with a template in this case. Something like...

# This is done to bring the variable into scope.
$search_dom = $::dns_new::params::searchdomny4
if $::oddip == 'true' {
  $dns_order = [ '1.1.1.1', '2.2.2.2' ]
} elsif $::oddip == 'false' {
  $dns_order = [ '2.2.2.2', '1.1.1.1' ]
}

file { '/etc/resolv.conf':
  [usual stuff]
  content => template('dns_new/resolv.conf.erb'),
}

With an ERB template that looks something like this:

new_dns/templates/resolv.conf.erb:

search <%= @search_dom %>
<% @dns_order.each do |serv| %>
nameserver <%= serv %>
<% end -%>

Which should emit a resolv.conf file in the order you want.

Another option is to just encode the DNS server list in the module, and use ruby code in the ERB template to determine if you walk down the array (each) or walk up it (reverse.each). That would look like:

$search_dom = $::dns_new::params::searchdomny4
$dns_list   = $::dns_new::params::dnslist
$oddip      = $::oddip
file { '/etc/resolv.conf':
  [usual stuff]
  content => template('dns_new/resolv.conf.erb')
}

With the more complex ERB formatted like:

search <%= @search_dom %>
<% if @odd_ip == 'true' %>
  <% @dns_list.each do |srv| -%>
nameserver <%= srv %>
  <% end -%>
<% elsif @odd_ip == 'false' -%>
  <% @dns_list.reverse.each do |srv| -%>
nameserver <%= srv %>
  <% end -%>
<% end -%>

In case you haven't done templates before, the key for the markup is roughly:

<%   : Here is ruby code.
<%=  : Here is an evaluated value. Replace this block with the 
         result, or enter a blank line.
-%>  : End a block. Don't render a blank line if it doesn't evaluate to anything.

The template will be evaluated line by line. The first line is a simple thing that drops the 'server' part of the resolv conf. The second line is where things get more complex: we're calling ruby functions. This is what allows us to drop as many nameserver lines as there are in the array.


I see what you're trying to do in the ERB, but I think you're over-complicating it. You're encoding the localization logic (nj vs ams vs lax) in the ERB itself. That can be done, but you might have better luck doing that part in the puppet-code. It's more likely to be readable to someone else if that part of the logic is in with the puppet code.

dns_new/manifests/config.pp:

case $::dcd {
  'ny4': {
            $search_domain = $::dns_new::params::searchdomny4
            $dns_list = [ "${::dns_new::params::ny4dns1}",    "${::dns_new::params::ny4dns2}" ]
         }
  'nj':  {
            $search_domain = $::dns_new::params::searchdomnj
            $dns_list = [ "${::dns_new::params::njdns1}",    "${::dns_new::params::njdns2}" ]
         }
  'ams2': {
            $search_domain = $::dns_new::params::searchdomams2
            $dns_list = [ "${::dns_new::params::ams2dns1}",    "${::dns_new::params::ams2dns2}" ]
          }
}

file { '/etc/resolv.conf':
  [the usual stuff]
  content = template('dns_new/resolv.conf.erb')
}

You now have two variables to deal with in the ERB. search_domain and dns_list. This shortens the ERB quite a bit:

dns_new/templates/resolv.conf.erb:

search <%= @search_domain %>
<% dns_list.each do |serv| -%>
nameserver <%= serv %>
<% end -%>

If you're wondering why I'm assigning variables in the class and using those in the ERB instead of using the variables in the params class, its because of the non-intuitive way that out-of-scope variables work in ERB files. It's much easier, and good style, to assign variables that will be used in an ERB file in the same class that calls the template.

11
  • Can you please explain this part: <% dns_order.each |serv| %> ?
    – Itai Ganot
    Aug 30, 2015 at 12:16
  • 2
    @ItaiGanot That's ruby code. It takes the array dns_order and applies the each function to it. The |serv| portion defines the variable that will be exposed in the loop, and will be set to the value of the array at each index. The loop ends at the first <% end %> call.
    – sysadmin1138
    Aug 30, 2015 at 12:23
  • I've followed your answer but I'm just not sure where to place each <% end %>...
    – Itai Ganot
    Aug 30, 2015 at 13:54
  • I want everything between the if and the end to be in the same segment... which is why I don't understand how to place the <% end %>'s
    – Itai Ganot
    Aug 30, 2015 at 13:57
  • @ItaiGanot See my updated answer, it has a solution that is a better fit for you.
    – sysadmin1138
    Aug 30, 2015 at 14:17
3

The "before" metaparameter indeed only says something about the order of execution of the resources, not the ordering of the lines in the file.

If I were you, I would aim to manage resolv.conf with first class puppet constructs: a seperate module that manages it as a file-resource (there probably are several on puppet forge), or write your own small template that explicitly orders the specified name server parameters.

Another option would be to specify both name servers in one file_line resource using \n to seperate them:

file_line { "ny4dns2 first":
  ensure => present,
  line   => "${::dns_new::params::ny4dns1}\n${::dns_new::params::ny4dns2}",
  path   => "/etc/resolv.conf",
  require => File_line["ny4 search domain"],
  after =>  "${::dns_new::params::searchdomny4}"
 }

This will not reorder them if they're in the wrong order, but it'll at least add them in the correct order right below the search statement (possibly listing 4 name servers which is more than 3 (MAXNS in resolv.h), but with just file_line resources avoiding this might be hard or impossible).

Also, the after parameter is specific to file_line resources, hinting where to insert the line, and before is a general resource parameter talking about resource ordering.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .