Создание пользовательских плагинов Ohai
Вы можете написать пользовательские плагины Ohai для сбора дополнительных атрибутов конфигурации с помощью Ohai, чтобы предоставить их Chef Infra Client во время выполнения.
Плагины Ohai написаны на Ruby с использованием описанного ниже DSL для плагинов. Написание на Ruby предоставляет доступ ко всем встроенным функциям Ruby, а также функциям сторонних библиотек. Плагины могут анализировать вывод любых локальных команд на узле или получать данные из внешних API. Примеры плагинов, написанных пользователями: — плагин для сбора информации об узле, включая данные о дата-центре, стойке и позиции стойки из сервера инвентаризации; — плагин для сбора дополнительной информации об массивах RAID из утилиты контроллера; — плагин для сбора информации о гарантии оборудования из API поставщика.
См. О плагинах Ohai для получения информации о конфигурации и использовании Ohai.
Установка плагинов Ohai
Установите пользовательские плагины Ohai, создав директорию ohai в вашем кулинарном руководстве и сохранив код плагина в этом месте.
Для миграции пользовательских плагинов Ohai из устаревшего кулинарного руководства ohai:
- Создайте директорию
ohaiв вашем кулинарном руководстве - Переместите ваш плагин в директорию
ohai - Удалите устаревший код установки пользовательских плагинов 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/