Puppet: Como definir a ordem de linha dentro de um arquivo?

2

Os servidores Linux da minha empresa são gerenciados pelo Puppet.

Há um módulo DNS que configura /etc/resolv.conf em todos os servidores com base na localização física, configurada como facter .

Como você sabe, um arquivo /etc/resolv.conf é assim:

search domain.local
nameserver 1.1.1.1
nameserver 2.2.2.2

Todos os nomes de host dos servidores na empresa terminam com dois dígitos, por exemplo:

proxy73

Para dividir o tráfego de rede DNS entre os dois servidores DNS, escrevi um novo módulo de fantoches que corta os dois últimos dígitos do nome do host e, se for um número ímpar, o arquivo /etc/resolv.conf deve ser semelhante ao mostrado acima , mas se os dígitos criarem um número ímpar, o arquivo /etc/resolv.conf deverá ser assim:

search domain.local
nameserver 2.2.2.2
nameserver 1.1.1.1

Mas meu problema é que não importa como eu escrevo o manifesto, as linhas são sempre ordenadas como o primeiro servidor e depois o segundo em vez do segundo servidor e depois o primeiro servidor.

A parte relevante do manifesto que escrevi se parece com essa (por favor, refira-se à parte em if $::oddip == false causa, que é a parte que não funciona):

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"],
          }
      }
    }

Você pode ver que eu tentei definir o pedido usando a diretiva before .

Isso é tudo em relação à configuração de um novo servidor, mas como posso definir a ordem das linhas para um servidor já instalado?

Editar # 2:

sysadmin1183, eu adicionei o "do" como você mostrou, agora o erro é este:

[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

A 27ª linha é:

<% end %>

Tentei editá-lo para:

<% end -%>

Mas obtendo a mesma saída ...

Editar # 3: config.pp parece assim:

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}" ]
        }
    }

O arquivo resolv.conf.erb tem esta aparência:

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

Execução do fantoche:

[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
    
por Itai Ganot 30.08.2015 / 13:16

2 respostas

5

Você pode ser mais bem servido com um modelo nesse caso. Algo como ...

# 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'),
}

Com um modelo ERB semelhante a este:

new_dns/templates/resolv.conf.erb :

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

Qual deve emitir um arquivo resolv.conf na ordem que você deseja.

Outra opção é simplesmente codificar a lista de servidores DNS no módulo e usar o código ruby no modelo ERB para determinar se você percorre o array (cada) ou o percorre (reverse.each). Isso seria parecido com:

$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')
}

Com o ERB mais complexo formatado como:

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 -%>

Caso você não tenha feito modelos antes, a chave da marcação é aproximadamente:

<%   : 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.

O modelo será avaliado linha por linha. A primeira linha é uma coisa simples que elimina a parte 'servidor' do comando conf. A segunda linha é onde as coisas ficam mais complexas: estamos chamando funções de ruby. Isso é o que nos permite descartar o máximo de nameserver linhas que existem no array.

Eu vejo o que você está tentando fazer no ERB, mas acho que você está complicando demais. Você está codificando a lógica de localização (nj vs ams vs lax) no próprio ERB. Isso pode ser feito, mas você pode ter mais sorte fazendo essa parte no código dos fantoches. É mais provável que seja legível para outra pessoa se essa parte da lógica estiver dentro do código do fantoche.

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')
}

Agora você tem duas variáveis para lidar no ERB. search_domain e dns_list . Isso encurta bastante o ERB:

dns_new/templates/resolv.conf.erb :

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

Se você está se perguntando por que eu estou atribuindo variáveis na classe e usando aquelas no ERB ao invés de usar as variáveis na classe params, é por causa da maneira não-intuitiva que as variáveis fora do escopo trabalham Arquivos ERB. É muito mais fácil e bom estilo atribuir variáveis que serão usadas em um arquivo ERB na mesma classe que chama o modelo.

    
por 30.08.2015 / 13:52
3

O metaparameter "antes" de fato apenas diz algo sobre a ordem de execução dos recursos, não a ordenação das linhas no arquivo.

Se eu fosse você, eu teria como objetivo gerenciar o resolv.conf com construções de marionetes de primeira classe: um módulo separado que o administra como um recurso de arquivo (provavelmente há vários no forjamento de fantoches) ou escrever seu próprio modelo pequeno ordena explicitamente os parâmetros do servidor de nomes especificados.

Outra opção seria especificar os dois servidores de nomes em um recurso de file_line usando \n para separá-los:

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}"
 }

Isso não os reorganizará se estiverem na ordem errada, mas pelo menos os adicionará na ordem correta logo abaixo da declaração de pesquisa (possivelmente listando 4 servidores de nomes que são mais de 3 (MAXNS em resolução). h), mas com apenas recursos file_line evitando isso pode ser difícil ou impossível.

Além disso, o parâmetro after é específico dos recursos file_line, indicando onde inserir a linha e before é um parâmetro de recurso geral que fala sobre a ordenação de recursos.

    
por 30.08.2015 / 13:45