chef/cookbooks/smf/libraries/xml_builder.rb
2015-07-21 19:45:23 +02:00

210 lines
7.1 KiB
Ruby

## This is kind of a hack, to ensure that the cookbook can be
# loaded. On first load, nokogiri may not be present. It is
# installed at load time by recipes/default.rb, so that at run
# time nokogiri will be present.
#
require 'forwardable'
# rubocop:disable Metrics/ClassLength
module SMFManifest
# XMLBuilder manages the translation of the SMF Chef resource attributes into
# XML that can be parsed by `svccfg import`.
#
# SMFManifest::XMLBuilder.new(resource, node).to_xml
#
class XMLBuilder
# allow delegation
extend Forwardable
attr_reader :resource, :node
# delegate methods to :resource
def_delegators :resource, :name, :authorization_name, :dependencies, :duration, :environment, :group, :ignore,
:include_default_dependencies, :locale, :manifest_type, :project, :property_groups,
:service_path, :stability, :working_directory
public
def initialize(smf_resource, node)
@resource = smf_resource
@node = node
end
def to_xml
@xml_output ||= xml_output
end
protected
## methods that need to be called from within the context
# of the Nokogiri builder block need to be protected, rather
# than private.
def commands
@commands ||= {
'start' => resource.start_command,
'stop' => resource.stop_command,
'restart' => resource.restart_command,
'refresh' => resource.refresh_command
}
end
def timeout
@timeouts ||= {
'start' => resource.start_timeout,
'stop' => resource.stop_timeout,
'restart' => resource.restart_timeout,
'refresh' => resource.refresh_timeout
}
end
def default_dependencies
if node.platform == 'solaris2' && node.platform_version == '5.11'
[
{ 'name' => 'milestone', 'value' => '/milestone/config' },
{ 'name' => 'fs-local', 'value' => '/system/filesystem/local' },
{ 'name' => 'name-services', 'value' => '/milestone/name-services' },
{ 'name' => 'network', 'value' => '/milestone/network' }
]
else
[
{ 'name' => 'milestone', 'value' => '/milestone/sysconfig' },
{ 'name' => 'fs-local', 'value' => '/system/filesystem/local' },
{ 'name' => 'name-services', 'value' => '/milestone/name-services' },
{ 'name' => 'network', 'value' => '/milestone/network' }
]
end
end
private
def xml_output
xml_builder = ::Builder::XmlMarkup.new(indent: 2)
xml_builder.instruct!
xml_builder.declare! :DOCTYPE, :service_bundle, :SYSTEM, '/usr/share/lib/xml/dtd/service_bundle.dtd.1'
xml_builder.service_bundle('name' => name, 'type' => 'manifest') do |xml|
xml.service('name' => service_fmri, 'type' => 'service', 'version' => '1') do |service|
service.create_default_instance('enabled' => 'false')
service.single_instance
if include_default_dependencies
default_dependencies.each do |dependency|
service.dependency('name' => dependency['name'],
'grouping' => 'require_all',
'restart_on' => 'none',
'type' => 'service') do |dep|
dep.service_fmri('value' => "svc:#{dependency['value']}")
end
end
end
dependencies.each do |dependency|
service.dependency('name' => dependency['name'],
'grouping' => dependency['grouping'],
'restart_on' => dependency['restart_on'],
'type' => dependency['type']) do |dep|
dependency['fmris'].each do |service_fmri|
dep.service_fmri('value' => service_fmri)
end
end
end
service.method_context(exec_context) do |context|
context.method_credential(credentials) if user != 'root'
if environment
context.method_environment do |env|
environment.each_pair do |var, value|
env.envvar('name' => var, 'value' => value)
end
end
end
end
commands.each_pair do |type, command|
if command
service.exec_method('type' => 'method', 'name' => type, 'exec' => command, 'timeout_seconds' => timeout[type])
end
end
service.property_group('name' => 'general', 'type' => 'framework') do |group|
group.propval('name' => 'action_authorization',
'type' => 'astring',
'value' => "solaris.smf.manage.#{authorization_name}")
group.propval('name' => 'value_authorization',
'type' => 'astring',
'value' => "solaris.smf.value.#{authorization_name}")
end
if sets_duration? || ignores_faults?
service.property_group('name' => 'startd', 'type' => 'framework') do |group|
group.propval('name' => 'duration', 'type' => 'astring', 'value' => duration) if sets_duration?
group.propval('name' => 'ignore_error', 'type' => 'astring', 'value' => ignore.join(',')) if ignores_faults?
end
end
property_groups.each_pair do |name, properties|
service.property_group('name' => name, 'type' => properties.delete('type') { |_type| 'application' }) do |group|
properties.each_pair do |key, value|
group.propval('name' => key, 'value' => value, 'type' => check_type(value))
end
end
end
service.stability('value' => stability)
service.template do |template|
template.common_name do |common_name|
common_name.loctext(name, 'xml:lang' => locale)
end
end
end
end
xml_builder.target!
end
def credentials
creds = { 'user' => user, 'privileges' => resource.privilege_list }
creds.merge!('group' => group) unless group.nil?
creds
end
def user
resource.user || resource.credentials_user || 'root'
end
def exec_context
context = {}
context['working_directory'] = working_directory unless working_directory.nil?
context['project'] = project unless project.nil?
context
end
def check_type(value)
if value == value.to_i
'integer'
else
'astring'
end
end
def ignores_faults?
!ignore.nil?
end
def sets_duration?
duration != 'contract'
end
# resource.fmri is set in the SMF :install action of the default provider.
# If there is already a service with a name that is matched by our resource.name
# then we grab the FMRI (fault management resource identifier) from the system.
# If a service is not found, we set this to our own FMRI.
def service_fmri
resource.fmri.nil? || resource.fmri.empty? ? "#{manifest_type}/management/#{name}" : resource.fmri.gsub(/^\//, '')
end
end
end
# rubocop:enable Metrics/ClassLength