Назад | Перейти на главную страницу

Puppet: проверка наборов переменных

Редактировать: Я придумал, как мне кажется, лучший способ решить эту проблему. Спасибо людям, ответившим на мой вопрос - помогло по ходу дела. Я собираюсь опубликовать свое решение, чтобы другие могли его найти, если захотят.

Исходный вопрос:

Я хотел бы использовать марионетку для автоматической настройки ряда наших файловых систем NFS. Однако я бы хотел, чтобы в манифесте был указан IP-адрес в правильной сети, прежде чем это делать.

Puppet помогает предоставить факты об IP-адресах, которые машина назначила через interfaces и ipaddress_*** факты. Проблема в том, что мне нужно проверить все интерфейсы (я бы не хотел делать предположений о том, как они связаны).

Вопрос 1: Может ли Puppet просмотреть список фактов? Например:

for $if in $interfaces {
     //do something
}

Вопрос 2: если Puppet может это сделать, есть ли способ оценить строку как переменную. interfaces дает действительно хороший способ узнать, сколько ipaddress_*** и network_***переменные есть, но даже если бы я мог перебирать их значения, мне нужно было бы сделать что-то вроде:

for $if in $interfaces {
    $net_var = eval("network_${if}")
    if $net_var = /^192\.168\.2\.0$/ {
        //do something
    }
}

Это возможно? Я ошибаюсь (что-то новенькое в Puppet)?

Что я в итоге сделал:

Puppet позволяет вам определять собственные функции парсера для модуля. Эти пользовательские функции просто рубиновые, поэтому вы можете эффективно делать в них все, что захотите, включая цикл.

Вы помещаете их в <module_dir>/lib/puppet/parser/functions/. Чтобы делать то, что хотел, я создал <module_dir>/lib/puppet/parser/functions/has_network.rb со следующим содержанием:

#has_network.rb
require 'ipaddr'

module Puppet::Parser::Functions
    newfunction(:has_network, :type => :rvalue) do |args|

        retcon = ""

        if not /^[0-9]{1,3}(\.[0-9]{1,3}){3}\/[0-2]?[0-9]$/.match(args[0]) then
            raise Puppet::ParseError, "Network provided was not valid. Provide 192.168.0.0/16 format" 
        else
            requested_network = IPAddr.new(args[0])

            interfaces_fact =  lookupvar('interfaces')
            interfaces = interfaces_fact.split(",")
            interfaces.each do |interface|
                ip = IPAddr.new(lookupvar("ipaddress_#{interface}"))
                if requested_network.include?(ip)
                    retcon = "true"
                end
            end
        end

        retcon
    end
end

Это добавляет новую функцию has_network, которую я могу использовать в своих манифестах. Он возвращает истину, если один из IP-адресов машины находится в сети, предоставленной функции. Теперь я могу делать следующее:

if has_network("192.168.1.0/24"){
    //do something
}

Несколько замечаний о пользовательских функциях

Как упоминалось в @Zoredache, в Puppet нет цикла. Но вы можете обойти это, используя определение, например:

define show_ip {
    $inf = "ipaddress_${name}"
    $ip = inline_template('<%= scope.lookupvar(inf) %>')

    if $ip =~ /192\.168\.3.*/ {
        notify {"Found IP $ip": }
    }
}

$ifs = split($interfaces, ',')

show_ip { $ifs: }

Belows - это выход при прямом вызове:

# puppet manifests/defines/net.pp 
warning: Implicit invocation of 'puppet apply' by passing files (or flags) directly
to 'puppet' is deprecated, and will be removed in the 2.8 series.  Please
invoke 'puppet apply' directly in the future.

notice: Found IP 192.168.3.118
notice: /Stage[main]//Show_ip[eth1]/Notify[Found IP 192.168.3.118]/message: defined 'message' as 'Found IP 192.168.3.118'
notice: Finished catalog run in 0.07 seconds

Источник: https://blog.kumina.nl/tag/puppet-tips-and-tricks/

В марионетке нет цикла или итераций.

Еще немного поигрался, и вот альтернативная версия, которая должна работать для любого количества IP-адресов на одном интерфейсе и не использует eval. to_hash.keys возвращает массив имен всех переменных, find_all фильтрует список на основе регулярного выражения, mapping используется для поиска фактического значения переменной, и, наконец, результаты объединяются обратно в строку.

$iplist=inline_template('<%= scope.to_hash.keys.find_all{|i| i =~ /ipaddress_.*/}.map{|j| scope.lookupvar(j)}.join(",")%>')
if $iplist =~ /10\.2\.93.*/ {
      notify {"found $iplist": }
}
# on the client with a matching address we see "notice: found 10.2.93.5,127.0.0.1"

У меня есть кое-что, что работает, но я не уверен, что это действительно лучшее или безопасное решение. В принципе, я полагаю, вы можете использовать inline_template. Внутри встроенного шаблона мы разделяем факт интерфейсов, который представляет собой список всех интерфейсов. Мы используем сопоставление для создания нового массива IP-адресов всех интерфейсов и, наконец, снова объединяем массив в строку.

Больше всего меня беспокоит эта идея, потому что я выполняю eval для чего-то, что в основном предоставляется клиентской системой. Я не совсем уверен, насколько это зло.

Это также сломается, если интерфейс имеет несколько адресов или если интерфейс не имеет адресов. Может быть, кто-то еще может предложить какие-то улучшения. Я только изучаю рубин.

$iplist=inline_template('<%= interfaces.split(",").map{|x| eval ("ipaddress_"+x)}.join(",")%>')

if $iplist =~ /10\.2\.93.*/ {
      notify {"found $iplist": }
}
# on the client with a matching address we see "notice: found 10.2.93.5,127.0.0.1"