Spec-Zone.ru › Chef 18

Создание пользовательских плагинов Ohai

[править на GitHub]

Вы можете написать пользовательские плагины Ohai для сбора дополнительных атрибутов конфигурации с помощью Ohai, чтобы предоставить их Chef Infra Client во время выполнения.

Плагины Ohai написаны на Ruby с использованием описанного ниже DSL для плагинов. Написание на Ruby предоставляет доступ ко всем встроенным функциям Ruby, а также функциям сторонних библиотек. Плагины могут анализировать вывод любых локальных команд на узле или получать данные из внешних API. Примеры плагинов, написанных пользователями: — плагин для сбора информации об узле, включая данные о дата-центре, стойке и позиции стойки из сервера инвентаризации; — плагин для сбора дополнительной информации об массивах RAID из утилиты контроллера; — плагин для сбора информации о гарантии оборудования из API поставщика.

См. О плагинах Ohai для получения информации о конфигурации и использовании Ohai.

Установка плагинов Ohai

Установите пользовательские плагины Ohai, создав директорию ohai в вашем кулинарном руководстве и сохранив код плагина в этом месте.

Для миграции пользовательских плагинов Ohai из устаревшего кулинарного руководства ohai:

  1. Создайте директорию ohai в вашем кулинарном руководстве
  2. Переместите ваш плагин в директорию ohai
  3. Удалите устаревший код установки пользовательских плагинов Ohai из вашего кода

Chef Infra Client переместит файл в правильное место, загрузит его и вернёт данные в следующей конфигурации. Chef Infra Client предоставит другие кулинарные руководства, которые зависят от пользовательского плагина Ohai, с правильными данными.

Синтаксис

Синтаксис плагина Ohai следующий:

Ohai.plugin(:Name) do
  provides 'attribute', 'attribute/subattribute'
  depends 'attribute', 'attribute'

  def shared_method
    # some Ruby code that defines the shared method
    attribute my_data
  end

  collect_data(:default) do
    # some Ruby code
    attribute my_data
  end

  collect_data(:platform...) do
    # some Ruby code that defines platform-specific requirements
    attribute my_data
  end
end

где

  • Обязательно. (:Name) используется для идентификации плагина; когда два плагина имеют одинаковое значение (:Name), эти плагины объединяются и выполняются как один плагин. Это значение должно быть допустимым именем класса Ruby, начинающимся с большой буквы и содержащим только буквенно-цифровые символы
  • Обязательно. provides — это список атрибутов, разделенных запятыми (один или несколько), которые определены этим плагином. Этот атрибут станет автоматическим атрибутом (node['attribute']) после сбора Ohai в начале выполнения Chef Infra Client. Атрибут также может быть определен с использованием шаблона attribute/subattribute
  • depends — это список атрибутов, разделенных запятыми (один или несколько), которые собираются другим плагином; пока значение собирается другим плагином Ohai, им может воспользоваться любой плагин
  • shared_method определяет код, который может быть использован в одном (или нескольких) блоках collect_data; например, вместо определения хеша для каждого блока collect_data, код может быть определён как общий метод, а затем вызван из любого блока collect_data
  • collect_data — это блок кода Ruby, вызываемый Ohai при запуске; в плагине может быть определён один (или несколько) блоков collect_data, но выполняется только один блок collect_data
  • collect_data(:default) — это блок кода, который выполняется, когда платформа узла не определена блоком collect_data, специфичным для платформы
  • collect_data(:platform) — это блок кода, специфичный для платформы, который выполняется, когда существует соответствие между платформой узла и этим блоком collect_data; может существовать только один блок collect_data для каждой платформы; возможные значения: :aix, :darwin, :freebsd, :linux, :openbsd, :netbsd, :solaris2, :windows, или любое другое значение из RbConfig::CONFIG['host_os']
  • my_data — строка (a string value) или пустой хеш ({ :setting_a => 'value_a', :setting_b => 'value_b' }). Это используется для определения данных, которые должны быть собраны плагином

Например, следующий плагин ищет данные о виртуальных машинах, размещённых в Amazon EC2, Google Compute Engine, Rackspace, Eucalyptus, Linode, OpenStack и Microsoft Azure:

Ohai.plugin(:Cloud) do
  provides 'cloud'

  depends 'ec2'
  depends 'gce'
  depends 'rackspace'
  depends 'eucalyptus'
  depends 'linode'
  depends 'openstack'
  depends 'azure'

  def create_objects
    cloud Mash.new
    cloud[:public_ips] = []
    cloud[:private_ips] = []
  end

  ...

  def on_gce?
    gce != nil
  end

  def get_gce_values
    cloud[:public_ipv4] = []
    cloud[:local_ipv4] = []

    public_ips = gce['instance']['networkInterfaces'].collect do |interface|
      if interface.has_key?('accessConfigs')
        interface['accessConfigs'].collect{|ac| ac['externalIp']}
      end
    end.flatten.compact

    private_ips = gce['instance']['networkInterfaces'].collect do |interface|
      interface['ip']
    end.compact

    cloud[:public_ips] += public_ips
    cloud[:private_ips] += private_ips
    cloud[:public_ipv4] +=  public_ips
    cloud[:public_hostname] = nil
    cloud[:local_ipv4] += private_ips
    cloud[:local_hostname] = gce['instance']['hostname']
    cloud[:provider] = 'gce'
  end

  ...

  # with following similar code blocks for each cloud provider

где

  • provides определяет атрибут cloud, который затем преобразуется в объект с помощью общего метода create_objects, который затем генерирует хеш на основе общедоступных или закрытых IP-адресов
  • Для Google Compute Engine данные атрибута cloud заполняются в хеш на основе IP-адреса узла

Чтобы увидеть остальной код в этом плагине, перейдите по ссылке: https://github.com/chef/ohai/blob/main/lib/ohai/plugins/cloud.rb.

Методы Ohai

DSL Ohai — это DSL Ruby, который используется для определения плагина Ohai и для обеспечения того, чтобы Ohai собирал правильные данные в начале каждого выполнения Chef Infra Client. DSL Ohai — это небольшой DSL с одним методом, специфичным для плагинов Ohai. Поскольку DSL Ohai — это DSL Ruby, всё, что можно сделать с помощью Ruby, также можно сделать при определении плагина Ohai.

collect_data

Метод collect_data — это блок кода Ruby, вызываемый Ohai при его запуске. В плагине может быть определено один (или несколько) блоков collect_data, но выполняется только один блок collect_data. Блок collect_data, который выполняется, определяется платформой, на которой работает узел, которая затем сопоставляется с доступными блоками collect_data в плагине.

  • Блок collect_data(:default) используется, когда Ohai не может сопоставить платформу узла с блоком collect_data(:platform) в плагине
  • Блок collect_data(:platform) необходим для каждой платформы, требующей отличного от стандартного поведения

Когда Ohai выполняется, если нет соответствующего блока collect_data для платформы, используется блок collect_data(:default). Синтаксис метода collect_data:

collect_data(:default) do
  # some Ruby code
end

или:

collect_data(:platform) do
  # some Ruby code
end

где:

  • :default — имя блока collect_data по умолчанию
  • :platform — имя платформы, например, :aix для AIX или :windows для Microsoft Windows

Использование Mash

Используйте mash для хранения данных. Для этого создайте новый mash и установите атрибут в него. Например:

provides 'name_of_mash'
name_of_mash Mash.new
name_of_mash[:attribute] = 'value'

Примеры

Следующие примеры показывают, как использовать блок collect_data:

Ohai.plugin(:Azure) do
  provides 'azure'

  collect_data do
    azure_metadata_from_hints = hint?('azure')
    if azure_metadata_from_hints
      Ohai::Log.debug('azure_metadata_from_hints is present.')
      azure Mash.new
      azure_metadata_from_hints.each {|k, v| azure[k] = v }
    else
      Ohai::Log.debug('No hints present for azure.')
      false
    end
  end
end

или:

require 'ohai/mixin/ec2_metadata'
extend Ohai::Mixin::Ec2Metadata

Ohai.plugin do
  provides 'openstack'

  collect_data do
    if hint?('openstack') || hint?('hp')
      Ohai::Log.debug('ohai openstack')
      openstack Mash.new
      if can_metadata_connect?(EC2_METADATA_ADDR,80)
        Ohai::Log.debug('connecting to the OpenStack metadata service')
        self.fetch_metadata.each {|k, v| openstack[k] = v }
        case
        when hint?('hp')
          openstack['provider'] = 'hp'
        else
          openstack['provider'] = 'openstack'
        end
      else
        Ohai::Log.debug('unable to connect to the OpenStack metadata service')
      end
    else
      Ohai::Log.debug('NOT ohai openstack')
    end
  end
end

require

Метод require — это стандартный метод Ruby, который может использоваться для перечисления файлов, которые могут потребоваться платформе, например, внешней библиотеке классов. В качестве рекомендации, хотя метод require часто используется в начале файла Ruby, рекомендуется использовать метод require как часть блока collect_data специфичного для платформы. Например, Ruby WMI требуется для Microsoft Windows:

collect_data(:windows) do
  require 'ruby-wmi'
  WIN32OLE.codepage = WIN32OLE::CP_UTF8

  kernel Mash.new

  host = WMI::Win32_OperatingSystem.find(:first)
  kernel[:os_info] = Mash.new
  host.properties_.each do |p|
    kernel[:os_info][p.name.wmi_underscore.to_sym] = host.send(p.name)
  end

  ...

end

Ohai попытается полностью квалифицировать имя любого класса, добавив Ohai:: к загруженному классу. Например, оба:

require Ohai::Mixin::ShellOut

и:

require Mixin::ShellOut

понимаются Ohai одинаково: Ohai::Mixin::ShellOut.

Когда класс является внешним классом (и, следовательно, не должен иметь Ohai:: в начале), используйте :: чтобы уведомить Ohai. Например:

::External::Class::Library

/common Directory

Директория /common хранит код, используемый во всех плагинах Ohai. Например, файл в директории /common с именем virtualization.rb , который содержит код, подобный следующему:

module Ohai
  module Common
    module Virtualization

      def host?(virtualization)
        !virtualization.nil? && virtualization[:role].eql?('host')
      end

      def open_virtconn(system)
        begin
          require 'libvirt'
          require 'hpricot'
        rescue LoadError => e
          Ohai::Log.debug('Cannot load gem: #{e}.')
        end

        emu = (system.eql?('kvm') ? 'qemu' : system)
        virtconn = Libvirt::open_read_only('#{emu}:///system')
      end

      ...

      def networks(virtconn)
        networks = Mash.new
        virtconn.list_networks.each do |n|
          nv = virtconn.lookup_network_by_name n
          networks[n] = Mash.new
          networks[n][:xml_desc] = (nv.xml_desc.split('\n').collect {|line| line.strip}).join
          ['bridge_name','uuid'].each {|a| networks[n][a] = nv.send(a)}
          #xdoc = Hpricot networks[n][:xml_desc]
        end
        networks
      end

      ...

    end
  end
end

может быть использован в плагине с помощью метода require для загрузки файла virtualization.rb и последующего вызова каждого метода в загруженном модуле:

require 'ohai/common/virtualization'

Ohai.plugin(:Virtualization) do
  include Ohai::Common::Virtualization

  provides 'virtualization'
  %w{ capabilities domains networks storage }.each do |subattr|
    provides 'virtualization/#{subattr}'
  end

  collect_data(:linux) do
    virtualization Mash.new

    ...

    if host?(virtualization)
      v = open_virtconn(virtualization[:system])

      virtualization[:libvirt_version] = libvirt_version(v)
      virtualization[:nodeinfo] = nodeinfo(v)
      virtualization[:uri] = uri(v)
      virtualization[:capabilities] = capabilities(v)
      virtualization[:domains] = domains(v)
      virtualization[:networks] = networks(v)
      virtualization[:storage] = storage(v)

      close_virtconn(v)
    end

Общие методы

Используйте общие методы для определения объектов для использования в блоках collect_data, таких как структура данных, хеш или mash. Синтаксис общего метода:

def a_shared_method
  # some Ruby code that defines the shared method
end

Следующий пример объявляет общий метод cloud для сбора данных о поставщиках облачных услуг на основе типа IP-адреса и затем использует объект cloud для сбора данных от разных поставщиков облачных услуг.

Создайте объекты cloud на основе типа IP-адреса:

def create_objects
  cloud Mash.new
  cloud[:public_ips] = Array.new
  cloud[:private_ips] = Array.new
end

Используйте объект cloud для сбора данных Linode:

def get_linode_values
  cloud[:public_ips] << linode['public_ip']
  cloud[:private_ips] << linode['private_ip']
  cloud[:public_ipv4] = linode['public_ipv4']
  cloud[:public_hostname] = linode['public_hostname']
  cloud[:local_ipv4] = linode['local_ipv4']
  cloud[:local_hostname] = linode['local_hostname']
  cloud[:provider] = 'linode'
end

Используйте объект cloud для сбора данных Azure:

def get_azure_values
  cloud[:vm_name] = azure['vm_name']
  cloud[:public_ips] << azure['public_ip']
  cloud[:public_fqdn] = azure['public_fqdn']
  cloud[:public_ssh_port] = azure['public_ssh_port'] if azure['public_ssh_port']
  cloud[:public_winrm_port] = azure['public_winrm_port'] if azure['public_winrm_port']
  cloud[:provider] = 'azure'
end

Ведение журнала

Используйте класс Ohai::Log в плагине Ohai для определения записей журнала, созданных Ohai. Синтаксис сообщения журнала:

Ohai::Log.log_type('message')

где

  • log_type может быть .debug, .info, .warn, .error, или .fatal
  • 'message' — это сообщение, которое записывается в журнал.

Например:

Ohai.plugin do
  provides 'openstack'

  collect_data do
    if hint?('openstack') || hint?('hp')
      Ohai::Log.debug('ohai openstack')
      openstack Mash.new
      if can_metadata_connect?(EC2_METADATA_ADDR,80)
        Ohai::Log.debug('connecting to the OpenStack metadata service')
        self.fetch_metadata.each {|k, v| openstack[k] = v }
        case
        when hint?('hp')
          openstack['provider'] = 'hp'
        else
          openstack['provider'] = 'openstack'
        end
      else
        Ohai::Log.debug('unable to connect to the OpenStack metadata service')
      end
    else
      Ohai::Log.debug('NOT ohai openstack')
    end
  end
end

обработка исключений

Используйте блок rescue , чтобы убедиться, что сообщение в журнале всегда предоставляется. Например:

rescue LoadError => e
  Ohai::Log.debug('ip_scopes: cannot load gem, plugin disabled: #{e}')
end

Примеры

Следующие примеры показывают различные способы создания плагинов Ohai.

Блоки collect_data

Следующий плагин Ohai использует несколько блоков collect_data и общие методы для определения платформ:

Ohai.plugin(:Hostname) do
  provides 'domain', 'fqdn', 'hostname'

  def from_cmd(cmd)
    so = shell_out(cmd)
    so.stdout.split($/)[0]
  end

  def collect_domain
    if fqdn
      fqdn =~ /.+?\.(.*)/
      domain $1
    end
  end

  collect_data(:aix, :hpux) do
    hostname from_cmd('hostname -s')
    fqdn from_cmd('hostname')
    domain collect_domain
  end

  collect_data(:darwin, :netbsd, :openbsd) do
    hostname from_cmd('hostname -s')
    fqdn from_cmd('hostname')
    domain collect_domain
  end

  collect_data(:freebsd) do
    hostname from_cmd('hostname -s')
    fqdn from_cmd('hostname -f')
    domain collect_domain
  end

  collect_data(:linux) do
    hostname from_cmd('hostname -s')
    begin
      fqdn from_cmd('hostname --fqdn')
    rescue
      Ohai::Log.debug('hostname -f returned an error, probably no domain is set')
    end
    domain collect_domain
  end

  collect_data(:solaris2) do
    require 'socket'

    hostname from_cmd('hostname')

    fqdn_lookup = Socket.getaddrinfo(hostname, nil, nil, nil, nil, Socket::AI_CANONNAME).first[2]
    if fqdn_lookup.split('.').length > 1
      # we received an fqdn
      fqdn fqdn_lookup
    else
      # default to assembling one
      h = from_cmd('hostname')
      d = from_cmd('domainname')
      fqdn '#{h}.#{d}'
    end

    domain collect_domain
  end

  collect_data(:windows) do
    require 'ruby-wmi'
    require 'socket'

    host = WMI::Win32_ComputerSystem.find(:first)
    hostname '#{host.Name}'

    info = Socket.gethostbyname(Socket.gethostname)
    if info.first =~ /.+?\.(.*)/
      fqdn info.first
    else
      # host is not in dns. optionally use:
      # C:\WINDOWS\system32\drivers\etc\hosts
      fqdn Socket.gethostbyaddr(info.last).first
    end

   domain collect_domain
  end
end

Использование библиотеки миксинов

Следующий пример плагина Ohai показывает, как плагин может использовать библиотеку mixin и также зависеть от другого плагина:

require 'ohai/mixin/os'

Ohai.plugin(:Os) do
  provides 'os', 'os_version'
  depends 'kernel'

  collect_data do
    os collect_os
    os_version kernel[:release]
  end
end

Получение значений ядра

Следующий пример плагина Ohai показывает часть файла, которая получает начальные значения атрибутов ядра:

Ohai.plugin(:Kernel) do
  provides 'kernel', 'kernel/modules'

  def init_kernel
    kernel Mash.new
    [['uname -s', :name], ['uname -r', :release],
    ['uname -v', :version], ['uname -m', :machine]].each do |cmd, property|
      so = shell_out(cmd)
      kernel[property] = so.stdout.split($/)[0]
    end
    kernel
  end

  ...

  collect_data(:darwin) do
    kernel init_kernel
    kernel[:os] = kernel[:name]

    so = shell_out('sysctl -n hw.optional.x86_64')
    if so.stdout.split($/)[0].to_i == 1
      kernel[:machine] = 'x86_64'
    end

    modules = Mash.new
    so = shell_out('kextstat -k -l')
    so.stdout.lines do |line|
      if line =~ /(\d+)\s+(\d+)\s+0x[0-9a-f]+\s+0x([0-9a-f]+)\s+0x[0-9a-f]+\s+([a-zA-Z0-9\.]+) \(([0-9\.]+)\)/
        kext[$4] = { :version => $5, :size => $3.hex, :index => $1, :refcount => $2 }
      end
    end

    kernel[:modules] = modules
  end

  ...

© Chef Software, Inc.
Licensed under the Creative Commons Attribution 3.0 Unported License.
The Chef™ Mark and Chef Logo are either registered trademarks/service marks or trademarks/servicemarks of Chef, in the United States and other countries and are used with Chef Inc's permission.
We are not affiliated with, endorsed or sponsored by Chef Inc.
https://docs.chef.io/ohai_custom/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API