Revision 3484613f
Added by Joseph Magen almost 11 years ago
- ID 3484613f7fb711c65724261720048218427fe670
app/controllers/api/api_responder.rb | ||
---|---|---|
module Api
|
||
class ApiResponder < ActionController::Responder
|
||
|
||
# overview api_behavior
|
||
def api_behavior(error)
|
||
raise error unless resourceful?
|
||
|
||
if !get? && !post?
|
||
#return resource instead of default "head :no_content" for PUT, PATCH, and DELETE
|
||
display resource
|
||
else
|
||
super
|
||
end
|
||
|
||
end
|
||
end
|
||
end
|
app/mailers/host_mailer.rb | ||
---|---|---|
class HostMailer < ActionMailer::Base
|
||
helper :reports
|
||
|
||
default :content_type => "text/html", :from => Setting[:email_reply_address] || "noreply@foreman.exmaple.org"
|
||
# sends out a summary email of hosts and their metrics (e.g. how many changes failures etc).
|
||
|
||
|
||
def summary(options = {})
|
||
# currently we send to all registered users or to the administrator (if LDAP is disabled).
|
||
# TODO add support to host / group based emails.
|
||
|
||
# options our host list if required
|
||
filter = []
|
||
|
||
if (@url = Setting[:foreman_url]).empty?
|
||
raise ::Foreman::Exception.new N_("':foreman_url:' entry in Foreman configuration file, see http://theforeman.org/projects/foreman/wiki/Mail_Notifications")
|
||
end
|
||
|
||
if options[:env]
|
||
hosts = envhosts = options[:env].hosts
|
||
raise (_("unable to find any hosts for puppet environment=%s") % env) if envhosts.size == 0
|
||
filter << "Environment=#{options[:env].name}"
|
||
end
|
||
name,value = options[:factname],options[:factvalue]
|
||
if name and value
|
||
facthosts = Host.search_for("facts.#{name}=#{value}")
|
||
raise (_("unable to find any hosts with the fact name=%{name} and value=%{value}") % { :name => name, :value => value }) if facthosts.empty?
|
||
filter << "Fact #{name}=#{value}"
|
||
# if environment and facts are defined together, we use a merge of both
|
||
hosts = envhosts.empty? ? facthosts : envhosts & facthosts
|
||
end
|
||
|
||
if hosts.empty?
|
||
# print out an error if we couldn't find any hosts that match our request
|
||
raise ::Foreman::Exception.new(N_("unable to find any hosts that match your request")) if options[:env] or options[:factname]
|
||
# we didnt define a filter, use all hosts instead
|
||
hosts = Host::Managed
|
||
end
|
||
email = options[:email] || Setting[:administrator]
|
||
raise ::Foreman::Exception.new(N_("unable to find recipients")) if email.empty?
|
||
time = options[:time] || 1.day.ago
|
||
host_data = Report.summarise(time, hosts.all).sort
|
||
total_metrics = {"failed"=>0, "restarted"=>0, "skipped"=>0, "applied"=>0, "failed_restarts"=>0}
|
||
host_data.flatten.delete_if { |x| true unless x.is_a?(Hash) }.each do |data_hash|
|
||
total_metrics["failed"] += data_hash[:metrics]["failed"]
|
||
total_metrics["restarted"] += data_hash[:metrics]["restarted"]
|
||
total_metrics["skipped"] += data_hash[:metrics]["skipped"]
|
||
total_metrics["applied"] += data_hash[:metrics]["applied"]
|
||
total_metrics["failed_restarts"] += data_hash[:metrics]["failed_restarts"]
|
||
end
|
||
total = 0 ; total_metrics.values.each { |v| total += v }
|
||
subject = _("Summary Puppet report from Foreman - F:%{failed} R:%{restarted} S:%{skipped} A:%{applied} FR:%{failed_restarts} T:%{total}") % {
|
||
:failed => total_metrics["failed"],
|
||
:restarted => total_metrics["restarted"],
|
||
:skipped => total_metrics["skipped"],
|
||
:applied => total_metrics["applied"],
|
||
:failed_restarts => total_metrics["failed_restarts"],
|
||
:total => total_metrics["total"]
|
||
}
|
||
@hosts = host_data
|
||
@timerange = time
|
||
@out_of_sync = hosts.out_of_sync
|
||
@disabled = hosts.alerts_disabled
|
||
@filter = filter
|
||
mail(:to => email, :subject => subject)
|
||
end
|
||
|
||
def error_state(report)
|
||
host = report.host
|
||
email = host.owner.recipients if SETTINGS[:login] and not host.owner.nil?
|
||
email = Setting[:administrator] if email.empty?
|
||
raise ::Foreman::Exception.new(N_("unable to find recipients")) if email.empty?
|
||
@report = report
|
||
@host = host
|
||
mail(:to => email, :subject => (_("Puppet error on %s") % host.to_label))
|
||
end
|
||
end
|
app/models/authorization.rb | ||
---|---|---|
module Authorization
|
||
def self.included(base)
|
||
base.class_eval do
|
||
before_save :enforce_edit_permissions
|
||
before_destroy :enforce_destroy_permissions
|
||
before_create :enforce_create_permissions
|
||
end
|
||
end
|
||
|
||
# We must enforce the security model
|
||
def enforce_edit_permissions
|
||
enforce_permissions("edit") if enforce?
|
||
end
|
||
|
||
def enforce_destroy_permissions
|
||
enforce_permissions("destroy") if enforce?
|
||
end
|
||
|
||
def enforce_create_permissions
|
||
enforce_permissions("create") if enforce?
|
||
end
|
||
|
||
def enforce_permissions operation
|
||
# We get called again with the operation being set to create
|
||
return true if operation == "edit" and new_record?
|
||
|
||
klass = self.class.name.downcase
|
||
klasses = self.class.name.tableize
|
||
klasses.gsub!(/auth_source.*/, "authenticators")
|
||
klasses.gsub!(/common_parameters.*/, "global_variables")
|
||
klasses.gsub!(/lookup_key.*/, "external_variables")
|
||
klasses.gsub!(/lookup_value.*/, "external_variables")
|
||
return true if User.current and User.current.allowed_to?("#{operation}_#{klasses}".to_sym)
|
||
|
||
errors.add :base, _("You do not have permission to %{operation} this %{klass}") % { :operation => operation, :klass => klass }
|
||
@permission_failed = operation
|
||
false
|
||
end
|
||
|
||
# @return false or name of failed operation
|
||
def permission_failed?
|
||
return false unless @permission_failed
|
||
@permission_failed
|
||
end
|
||
|
||
private
|
||
def enforce?
|
||
return false if (User.current and User.current.admin?)
|
||
return true if defined?(Rake) and Rails.env == "test"
|
||
return false if defined?(Rake)
|
||
true
|
||
end
|
||
end
|
app/models/classification/base.rb | ||
---|---|---|
module Classification
|
||
class Base
|
||
delegate :hostgroup, :environment_id,
|
||
:to => :host
|
||
|
||
def initialize args = { }
|
||
@host = args[:host]
|
||
end
|
||
|
||
#override to return the relevant enc data and format
|
||
def enc
|
||
raise NotImplementedError
|
||
end
|
||
|
||
def inherited_values
|
||
values_hash :skip_fqdn => true
|
||
end
|
||
|
||
protected
|
||
|
||
attr_reader :host
|
||
|
||
#override this method to return the relevant parameters for a given set of classes
|
||
def class_parameters
|
||
raise NotImplementedError
|
||
end
|
||
|
||
def puppetclass_ids
|
||
return @puppetclass_ids if @puppetclass_ids
|
||
ids = host.host_classes.pluck(:puppetclass_id)
|
||
ids += HostgroupClass.where(:hostgroup_id => hostgroup.path_ids).pluck(:puppetclass_id) if hostgroup
|
||
@puppetclass_ids = if Setting['remove_classes_not_in_environment']
|
||
EnvironmentClass.where(:environment_id => host.environment_id, :puppetclass_id => ids).
|
||
pluck('DISTINCT puppetclass_id')
|
||
else
|
||
ids
|
||
end
|
||
end
|
||
|
||
def classes
|
||
Puppetclass.where(:id => puppetclass_ids)
|
||
end
|
||
|
||
def possible_value_orders
|
||
class_parameters.select do |key|
|
||
# take only keys with actual values
|
||
key.lookup_values_count > 0 # we use counter cache, so its safe to make that query
|
||
end.map(&:path_elements).flatten(1).uniq
|
||
end
|
||
|
||
def values_hash options={}
|
||
values = {}
|
||
path2matches.each do |match|
|
||
LookupValue.where(:match => match).where(:lookup_key_id => class_parameters.map(&:id)).each do |value|
|
||
key_id = value.lookup_key_id
|
||
values[key_id] ||= {}
|
||
key = class_parameters.detect{|k| k.id == value.lookup_key_id }
|
||
name = key.to_s
|
||
element = match.split(LookupKey::EQ_DELM).first
|
||
next if options[:skip_fqdn] && element=="fqdn"
|
||
if values[key_id][name].nil?
|
||
values[key_id][name] = {:value => value.value, :element => element}
|
||
else
|
||
if key.path.index(element) < key.path.index(values[key_id][name][:element])
|
||
values[key_id][name] = {:value => value.value, :element => element}
|
||
end
|
||
end
|
||
end
|
||
end
|
||
values
|
||
end
|
||
|
||
def value_of_key(key, values)
|
||
if values[key.id] and values[key.id][key.to_s]
|
||
values[key.id][key.to_s][:value]
|
||
else
|
||
key.default_value
|
||
end
|
||
end
|
||
|
||
def hostgroup_matches
|
||
@hostgroup_matches ||= matches_for_hostgroup
|
||
end
|
||
|
||
def matches_for_hostgroup
|
||
matches = []
|
||
if hostgroup
|
||
path = hostgroup.to_label
|
||
while path.include?("/")
|
||
path = path[0..path.rindex("/")-1]
|
||
matches << "hostgroup#{LookupKey::EQ_DELM}#{path}"
|
||
end
|
||
end
|
||
matches
|
||
end
|
||
|
||
# Generate possible lookup values type matches to a given host
|
||
def path2matches
|
||
matches = []
|
||
possible_value_orders.each do |rule|
|
||
match = Array.wrap(rule).map do |element|
|
||
"#{element}#{LookupKey::EQ_DELM}#{attr_to_value(element)}"
|
||
end
|
||
matches << match.join(LookupKey::KEY_DELM)
|
||
|
||
hostgroup_matches.each do |hostgroup_match|
|
||
match[match.index{|m|m =~ /hostgroup\s*=/}]=hostgroup_match
|
||
matches << match.join(LookupKey::KEY_DELM)
|
||
end if Array.wrap(rule).include?("hostgroup") && Setting["host_group_matchers_inheritance"]
|
||
end
|
||
matches
|
||
end
|
||
|
||
# translates an element such as domain to its real value per host
|
||
# tries to find the host attribute first, parameters and then fallback to a puppet fact.
|
||
def attr_to_value element
|
||
# direct host attribute
|
||
return host.send(element) if host.respond_to?(element)
|
||
# host parameter
|
||
return host.host_params[element] if host.host_params.include?(element)
|
||
# fact attribute
|
||
if (fn = host.fact_names.first(:conditions => { :name => element }))
|
||
return FactValue.where(:host_id => host.id, :fact_name_id => fn.id).first.value
|
||
end
|
||
end
|
||
|
||
def path_elements path = nil
|
||
path.split.map do |paths|
|
||
paths.split(LookupKey::KEY_DELM).map do |element|
|
||
element
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
app/models/classification/class_param.rb | ||
---|---|---|
module Classification
|
||
class ClassParam < Base
|
||
|
||
def enc
|
||
key_hash = hashed_class_parameters
|
||
values = values_hash
|
||
|
||
klasses = {}
|
||
classes.each do |klass|
|
||
klasses[klass.name] ||= {}
|
||
if key_hash[klass.id]
|
||
key_hash[klass.id].each do |key|
|
||
klasses[klass.name][key.to_s] = value_of_key(key, values)
|
||
end
|
||
else
|
||
klasses[klass.name] = nil
|
||
end
|
||
end
|
||
klasses
|
||
end
|
||
|
||
protected
|
||
def class_parameters
|
||
@keys ||= LookupKey.includes(:environment_classes).parameters_for_class(puppetclass_ids, environment_id)
|
||
end
|
||
|
||
private
|
||
def hashed_class_parameters
|
||
h = {}
|
||
class_parameters.each do |key|
|
||
klass_id = key.environment_classes.first.puppetclass_id
|
||
h[klass_id] ||= []
|
||
h[klass_id] << key
|
||
end
|
||
h
|
||
end
|
||
|
||
end
|
||
end
|
||
|
app/models/classification/global_param.rb | ||
---|---|---|
module Classification
|
||
class GlobalParam < Base
|
||
|
||
def enc
|
||
values = values_hash
|
||
|
||
parameters = {}
|
||
class_parameters.each do |key|
|
||
parameters[key.to_s] = value_of_key(key, values)
|
||
end
|
||
parameters
|
||
end
|
||
|
||
protected
|
||
def class_parameters
|
||
@keys ||= LookupKey.global_parameters_for_class(puppetclass_ids)
|
||
end
|
||
end
|
||
end
|
app/models/concerns/authorization.rb | ||
---|---|---|
module Authorization
|
||
def self.included(base)
|
||
base.class_eval do
|
||
before_save :enforce_edit_permissions
|
||
before_destroy :enforce_destroy_permissions
|
||
before_create :enforce_create_permissions
|
||
end
|
||
end
|
||
|
||
# We must enforce the security model
|
||
def enforce_edit_permissions
|
||
enforce_permissions("edit") if enforce?
|
||
end
|
||
|
||
def enforce_destroy_permissions
|
||
enforce_permissions("destroy") if enforce?
|
||
end
|
||
|
||
def enforce_create_permissions
|
||
enforce_permissions("create") if enforce?
|
||
end
|
||
|
||
def enforce_permissions operation
|
||
# We get called again with the operation being set to create
|
||
return true if operation == "edit" and new_record?
|
||
|
||
klass = self.class.name.downcase
|
||
klasses = self.class.name.tableize
|
||
klasses.gsub!(/auth_source.*/, "authenticators")
|
||
klasses.gsub!(/common_parameters.*/, "global_variables")
|
||
klasses.gsub!(/lookup_key.*/, "external_variables")
|
||
klasses.gsub!(/lookup_value.*/, "external_variables")
|
||
return true if User.current and User.current.allowed_to?("#{operation}_#{klasses}".to_sym)
|
||
|
||
errors.add :base, _("You do not have permission to %{operation} this %{klass}") % { :operation => operation, :klass => klass }
|
||
@permission_failed = operation
|
||
false
|
||
end
|
||
|
||
# @return false or name of failed operation
|
||
def permission_failed?
|
||
return false unless @permission_failed
|
||
@permission_failed
|
||
end
|
||
|
||
private
|
||
def enforce?
|
||
return false if (User.current and User.current.admin?)
|
||
return true if defined?(Rake) and Rails.env == "test"
|
||
return false if defined?(Rake)
|
||
true
|
||
end
|
||
end
|
app/models/concerns/host_common.rb | ||
---|---|---|
require 'securerandom'
|
||
|
||
#Common methods between host and hostgroup
|
||
# mostly for template rendering consistency
|
||
module HostCommon
|
||
def self.included(base)
|
||
base.send :include, InstanceMethods
|
||
base.class_eval do
|
||
belongs_to :architecture
|
||
belongs_to :environment
|
||
belongs_to :operatingsystem
|
||
belongs_to :medium
|
||
belongs_to :ptable
|
||
belongs_to :puppet_proxy, :class_name => "SmartProxy"
|
||
belongs_to :puppet_ca_proxy, :class_name => "SmartProxy"
|
||
belongs_to :domain
|
||
belongs_to :subnet
|
||
|
||
before_save :check_puppet_ca_proxy_is_required?
|
||
has_many :lookup_values, :finder_sql => Proc.new { %Q{ SELECT lookup_values.* FROM lookup_values WHERE (lookup_values.match = '#{lookup_value_match}') } }, :dependent => :destroy
|
||
# See "def lookup_values_attributes=" under, for the implementation of accepts_nested_attributes_for :lookup_values
|
||
accepts_nested_attributes_for :lookup_values
|
||
|
||
# Replacement of accepts_nested_attributes_for :lookup_values,
|
||
# to work around the lack of `host_id` column in lookup_values.
|
||
def lookup_values_attributes= lookup_values_attributes
|
||
lookup_values_attributes.each_value do |attribute|
|
||
attr = attribute.dup
|
||
if attr.has_key? :id
|
||
lookup_value = lookup_values.find attr.delete(:id)
|
||
if lookup_value
|
||
mark_for_destruction = ActiveRecord::ConnectionAdapters::Column.value_to_boolean attr.delete(:_destroy)
|
||
lookup_value.attributes = attr
|
||
mark_for_destruction ? lookup_values.delete(lookup_value) : lookup_value.save!
|
||
end
|
||
elsif !ActiveRecord::ConnectionAdapters::Column.value_to_boolean attr.delete(:_destroy)
|
||
LookupValue.create(attr.merge(:match => lookup_value_match))
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
module InstanceMethods
|
||
# Returns a url pointing to boot file
|
||
def url_for_boot file
|
||
"#{os.medium_uri(self)}/#{os.url_for_boot(file)}"
|
||
end
|
||
|
||
def puppetca?
|
||
return false if self.respond_to?(:managed?) and !managed?
|
||
!!(puppet_ca_proxy and puppet_ca_proxy.url.present?)
|
||
end
|
||
|
||
# no need to store anything in the db if the entry is plain "puppet"
|
||
# If the system is using smart proxies and the user has run the smartproxy:migrate task
|
||
# then the puppetmaster functions handle smart proxy objects
|
||
def puppetmaster
|
||
puppet_proxy.to_s
|
||
end
|
||
|
||
def puppet_ca_server
|
||
puppet_ca_proxy.to_s
|
||
end
|
||
|
||
# If the host/hostgroup has a medium then use the path from there
|
||
# Else if the host/hostgroup's operatingsystem has only one media then use the image_path from that as this is automatically displayed when there is only one item
|
||
# Else we cannot provide a default and it is cut and paste time
|
||
def default_image_file
|
||
return "" unless operatingsystem and operatingsystem.supports_image
|
||
if medium
|
||
nfs_path = medium.try :image_path
|
||
if operatingsystem.try(:media) and operatingsystem.media.size == 1
|
||
nfs_path ||= operatingsystem.media.first.image_path
|
||
end
|
||
# We encode the hw_model into the image file name as not all Sparc flashes can contain all possible hw_models. The user can always
|
||
# edit it if required or use symlinks if they prefer.
|
||
hw_model = model.try :hardware_model if defined?(model_id)
|
||
operatingsystem.interpolate_medium_vars(nfs_path, architecture.name, operatingsystem) +\
|
||
"#{operatingsystem.file_prefix}.#{architecture}#{hw_model.empty? ? "" : "." + hw_model.downcase}.#{operatingsystem.image_extension}"
|
||
else
|
||
""
|
||
end
|
||
end
|
||
|
||
def image_file= file
|
||
# We only save a value into the image_file field if the value is not the default path, (which was placed in the entry when it was displayed,)
|
||
# and it is not a directory, (ends in /)
|
||
value = ( (default_image_file == file) or (file =~ /\/$/) or file == "") ? nil : file
|
||
write_attribute :image_file, value
|
||
end
|
||
|
||
def image_file
|
||
super || default_image_file
|
||
end
|
||
|
||
# make sure we store an encrypted copy of the password in the database
|
||
# this password can be use as is in a unix system
|
||
def root_pass=(pass)
|
||
p = pass.empty? ? nil : (pass.starts_with?('$') ? pass : pass.crypt("$1$#{SecureRandom.base64(6)}"))
|
||
write_attribute(:root_pass, p)
|
||
end
|
||
|
||
private
|
||
|
||
# fall back to our puppet proxy in case our puppet ca is not defined/used.
|
||
def check_puppet_ca_proxy_is_required?
|
||
return true if puppet_ca_proxy_id.present? or puppet_proxy_id.blank?
|
||
if puppet_proxy.features.include?(Feature.find_by_name "Puppet CA")
|
||
self.puppet_ca_proxy ||= puppet_proxy
|
||
end
|
||
rescue
|
||
true # we don't want to break anything, so just skipping.
|
||
end
|
||
end
|
||
end
|
app/models/concerns/host_template_helpers.rb | ||
---|---|---|
# These helpers are provided as convenience methods available to the writers of templates
|
||
# and are mixed in to Host
|
||
module HostTemplateHelpers
|
||
# Calculates the media's path in relation to the domain and convert host to an IP
|
||
def install_path
|
||
operatingsystem.interpolate_medium_vars(operatingsystem.media_path(medium, domain), architecture.name, operatingsystem)
|
||
end
|
||
|
||
# Calculates the jumpstart path in relation to the domain and convert host to an IP
|
||
def jumpstart_path
|
||
operatingsystem.jumpstart_path medium, domain
|
||
end
|
||
|
||
def multiboot
|
||
operatingsystem.pxe_prefix(architecture) + "-multiboot"
|
||
end
|
||
|
||
def miniroot
|
||
operatingsystem.initrd(architecture)
|
||
end
|
||
|
||
def media_path
|
||
operatingsystem.medium_uri(self)
|
||
end
|
||
|
||
#returns the URL for Foreman based on the required action
|
||
def foreman_url(action = "provision")
|
||
url_for :only_path => false, :controller => "/unattended",
|
||
:action => action,
|
||
:token => (@host.token.value unless @host.token.nil?)
|
||
end
|
||
|
||
attr_writer(:url_options)
|
||
|
||
# used by url_for to generate the path correctly
|
||
def url_options
|
||
url_options = (@url_options || {}).deep_dup()
|
||
url_options[:protocol] = "http://"
|
||
url_options[:host] = Setting[:foreman_url] if Setting[:foreman_url]
|
||
url_options
|
||
end
|
||
|
||
end
|
app/models/concerns/hostext/search.rb | ||
---|---|---|
module Hostext
|
||
module Search
|
||
def self.included(base)
|
||
base.class_eval do
|
||
has_many :search_parameters, :class_name => 'Parameter', :foreign_key => :reference_id
|
||
belongs_to :search_users, :class_name => 'User', :foreign_key => :owner_id
|
||
|
||
scoped_search :on => :name, :complete_value => true, :default_order => true
|
||
scoped_search :on => :last_report, :complete_value => true, :only_explicit => true
|
||
scoped_search :on => :ip, :complete_value => true
|
||
scoped_search :on => :comment, :complete_value => true
|
||
scoped_search :on => :enabled, :complete_value => {:true => true, :false => false}, :rename => :'status.enabled'
|
||
scoped_search :on => :puppet_status, :offset => 0, :word_size => Report::BIT_NUM*4, :complete_value => {:true => true, :false => false}, :rename => :'status.interesting'
|
||
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("applied"), :word_size => Report::BIT_NUM, :rename => :'status.applied'
|
||
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("restarted"), :word_size => Report::BIT_NUM, :rename => :'status.restarted'
|
||
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("failed"), :word_size => Report::BIT_NUM, :rename => :'status.failed'
|
||
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("failed_restarts"), :word_size => Report::BIT_NUM, :rename => :'status.failed_restarts'
|
||
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("skipped"), :word_size => Report::BIT_NUM, :rename => :'status.skipped'
|
||
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("pending"), :word_size => Report::BIT_NUM, :rename => :'status.pending'
|
||
|
||
scoped_search :in => :model, :on => :name, :complete_value => true, :rename => :model
|
||
scoped_search :in => :hostgroup, :on => :name, :complete_value => true, :rename => :hostgroup
|
||
scoped_search :in => :hostgroup, :on => :label, :complete_value => true, :rename => :hostgroup_fullname
|
||
scoped_search :in => :domain, :on => :name, :complete_value => true, :rename => :domain
|
||
scoped_search :in => :environment, :on => :name, :complete_value => true, :rename => :environment
|
||
scoped_search :in => :architecture, :on => :name, :complete_value => true, :rename => :architecture
|
||
scoped_search :in => :puppet_proxy, :on => :name, :complete_value => true, :rename => :puppetmaster
|
||
scoped_search :in => :puppet_ca_proxy, :on => :name, :complete_value => true, :rename => :puppet_ca
|
||
scoped_search :in => :compute_resource, :on => :name, :complete_value => true, :rename => :compute_resource
|
||
scoped_search :in => :image, :on => :name, :complete_value => true
|
||
|
||
scoped_search :in => :puppetclasses, :on => :name, :complete_value => true, :rename => :class, :only_explicit => true, :operators => ['= ', '~ '], :ext_method => :search_by_puppetclass
|
||
scoped_search :in => :fact_values, :on => :value, :in_key=> :fact_names, :on_key=> :name, :rename => :facts, :complete_value => true, :only_explicit => true
|
||
scoped_search :in => :search_parameters, :on => :value, :on_key=> :name, :complete_value => true, :rename => :params, :ext_method => :search_by_params, :only_explicit => true
|
||
|
||
scoped_search :in => :location, :on => :name, :rename => :location, :complete_value => true if SETTINGS[:locations_enabled]
|
||
scoped_search :in => :organization, :on => :name, :rename => :organization, :complete_value => true if SETTINGS[:organizations_enabled]
|
||
|
||
if SETTINGS[:unattended]
|
||
scoped_search :in => :subnet, :on => :network, :complete_value => true, :rename => :subnet
|
||
scoped_search :on => :mac, :complete_value => true
|
||
scoped_search :on => :uuid, :complete_value => true
|
||
scoped_search :on => :build, :complete_value => {:true => true, :false => false}
|
||
scoped_search :on => :installed_at, :complete_value => true, :only_explicit => true
|
||
scoped_search :in => :operatingsystem, :on => :name, :complete_value => true, :rename => :os
|
||
scoped_search :in => :operatingsystem, :on => :major, :complete_value => true, :rename => :os_major
|
||
scoped_search :in => :operatingsystem, :on => :minor, :complete_value => true, :rename => :os_minor
|
||
end
|
||
|
||
if SETTINGS[:login]
|
||
scoped_search :in => :search_users, :on => :login, :complete_value => true, :only_explicit => true, :rename => :'user.login', :operators => ['= ', '~ '], :ext_method => :search_by_user
|
||
scoped_search :in => :search_users, :on => :firstname, :complete_value => true, :only_explicit => true, :rename => :'user.firstname',:operators => ['= ', '~ '], :ext_method => :search_by_user
|
||
scoped_search :in => :search_users, :on => :lastname, :complete_value => true, :only_explicit => true, :rename => :'user.lastname', :operators => ['= ', '~ '], :ext_method => :search_by_user
|
||
scoped_search :in => :search_users, :on => :mail, :complete_value => true, :only_explicit => true, :rename => :'user.mail', :operators => ['= ', '~ '], :ext_method => :search_by_user
|
||
end
|
||
|
||
def self.search_by_user(key, operator, value)
|
||
key_name = User.connection.quote_column_name(key.sub(/^.*\./,''))
|
||
condition = sanitize_sql_for_conditions(["#{key_name} #{operator} ?", value_to_sql(operator, value)])
|
||
users = User.all(:conditions => condition)
|
||
hosts = users.map(&:hosts).flatten
|
||
opts = hosts.empty? ? "< 0" : "IN (#{hosts.map(&:id).join(',')})"
|
||
|
||
return {:conditions => " hosts.id #{opts} " }
|
||
end
|
||
|
||
def self.search_by_puppetclass(key, operator, value)
|
||
conditions = sanitize_sql_for_conditions(["puppetclasses.name #{operator} ?", value_to_sql(operator, value)])
|
||
hosts = Host.my_hosts.all(:conditions => conditions, :joins => :puppetclasses, :select => 'DISTINCT hosts.id').map(&:id)
|
||
host_groups = Hostgroup.all(:conditions => conditions, :joins => :puppetclasses, :select => 'DISTINCT hostgroups.id').map(&:id)
|
||
|
||
opts = ''
|
||
opts += "hosts.id IN(#{hosts.join(',')})" unless hosts.blank?
|
||
opts += " OR " unless hosts.blank? || host_groups.blank?
|
||
opts += "hostgroups.id IN(#{host_groups.join(',')})" unless host_groups.blank?
|
||
opts = "hosts.id < 0" if hosts.blank? && host_groups.blank?
|
||
return {:conditions => opts, :include => :hostgroup}
|
||
end
|
||
|
||
def self.search_by_params(key, operator, value)
|
||
key_name = key.sub(/^.*\./,'')
|
||
condition = sanitize_sql_for_conditions(["name = ? and value #{operator} ?", key_name, value_to_sql(operator, value)])
|
||
opts = {:conditions => condition, :order => :priority}
|
||
p = Parameter.all(opts)
|
||
return {:conditions => '1 = 0'} if p.blank?
|
||
|
||
max = p.first.priority
|
||
condition = sanitize_sql_for_conditions(["name = ? and NOT(value #{operator} ?) and priority > ?",key_name,value_to_sql(operator, value), max])
|
||
negate_opts = {:conditions => condition, :order => :priority}
|
||
n = Parameter.all(negate_opts)
|
||
|
||
conditions = param_conditions(p)
|
||
negate = param_conditions(n)
|
||
|
||
conditions += " AND " unless conditions.blank? || negate.blank?
|
||
conditions += " NOT(#{negate})" unless negate.blank?
|
||
return {:conditions => conditions}
|
||
end
|
||
|
||
private
|
||
|
||
def self.param_conditions(p)
|
||
conditions = []
|
||
p.each do |param|
|
||
case param.class.to_s
|
||
when 'CommonParameter'
|
||
# ignore
|
||
when 'DomainParameter'
|
||
conditions << "hosts.domain_id = #{param.reference_id}"
|
||
when 'OsParameter'
|
||
conditions << "hosts.operatingsystem_id = #{param.reference_id}"
|
||
when 'GroupParameter'
|
||
conditions << "hosts.hostgroup_id = #{param.reference_id}"
|
||
when 'HostParameter'
|
||
conditions << "hosts.id = #{param.reference_id}"
|
||
end
|
||
end
|
||
conditions.empty? ? [] : "( #{conditions.join(' OR ')} )"
|
||
end
|
||
|
||
def self.value_to_sql(operator, value)
|
||
return value if operator !~ /LIKE/i
|
||
return value.tr_s('%*', '%') if (value =~ /%|\*/)
|
||
|
||
return "%#{value}%"
|
||
end
|
||
|
||
end
|
||
end
|
||
end
|
||
end
|
app/models/concerns/orchestration.rb | ||
---|---|---|
require "proxy_api"
|
||
require 'orchestration/queue'
|
||
|
||
module Orchestration
|
||
def self.included(base)
|
||
base.send :include, InstanceMethods
|
||
base.class_eval do
|
||
attr_reader :old
|
||
|
||
# save handles both creation and update of hosts
|
||
before_save :on_save
|
||
after_commit :post_commit
|
||
after_destroy :on_destroy
|
||
end
|
||
end
|
||
|
||
module InstanceMethods
|
||
|
||
protected
|
||
def on_save
|
||
process :queue
|
||
end
|
||
|
||
def post_commit
|
||
process :post_queue
|
||
end
|
||
|
||
def on_destroy
|
||
errors.empty? ? process(:queue) : false
|
||
end
|
||
|
||
def rollback
|
||
raise ActiveRecord::Rollback
|
||
end
|
||
|
||
# log and add to errors
|
||
def failure msg, backtrace=nil, dest = :base
|
||
logger.warn(backtrace ? msg + backtrace.join("\n") : msg)
|
||
errors.add dest, msg
|
||
false
|
||
end
|
||
|
||
public
|
||
|
||
# we override this method in order to include checking the
|
||
# after validation callbacks status, as rails by default does
|
||
# not care about their return status.
|
||
def valid?(context = nil)
|
||
setup_clone
|
||
super
|
||
orchestration_errors?
|
||
end
|
||
|
||
def queue
|
||
@queue ||= Orchestration::Queue.new
|
||
end
|
||
|
||
def post_queue
|
||
@post_queue ||= Orchestration::Queue.new
|
||
end
|
||
|
||
def record_conflicts
|
||
@record_conflicts ||= []
|
||
end
|
||
|
||
private
|
||
|
||
def proxy_error e
|
||
e.respond_to?(:message) ? e.message : e
|
||
end
|
||
# Handles the actual queue
|
||
# takes care for running the tasks in order
|
||
# if any of them fail, it rollbacks all completed tasks
|
||
# in order not to keep any left overs in our proxies.
|
||
def process queue_name
|
||
return true if Rails.env == "test"
|
||
|
||
# queue is empty - nothing to do.
|
||
q = send(queue_name)
|
||
return if q.empty?
|
||
|
||
# process all pending tasks
|
||
q.pending.each do |task|
|
||
# if we have failures, we don't want to process any more tasks
|
||
break unless q.failed.empty?
|
||
|
||
task.status = "running"
|
||
|
||
update_cache
|
||
begin
|
||
task.status = execute({:action => task.action}) ? "completed" : "failed"
|
||
|
||
rescue Net::Conflict => e
|
||
task.status = "conflict"
|
||
record_conflicts << e
|
||
failure e.message, nil, :conflict
|
||
#TODO: This is not a real error, but at the moment the proxy / foreman lacks better handling
|
||
# of the error instead of explode.
|
||
rescue Net::LeaseConflict => e
|
||
task.status = "failed"
|
||
failure _("DHCP has a lease at %s") % e, e.backtrace
|
||
rescue RestClient::Exception => e
|
||
task.status = "failed"
|
||
failure _("%{task} task failed with the following error: %{e}") % { :task => task.name, :e => proxy_error(e) }, e.backtrace
|
||
rescue => e
|
||
task.status = "failed"
|
||
failure _("%{task} task failed with the following error: %{e}") % { :task => task.name, :e => e }, e.backtrace
|
||
end
|
||
end
|
||
|
||
update_cache
|
||
# if we have no failures - we are done
|
||
return true if q.failed.empty? and q.pending.empty? and q.conflict.empty? and orchestration_errors?
|
||
|
||
logger.warn "Rolling back due to a problem: #{q.failed + q.conflict}"
|
||
q.pending.each{ |task| task.status = "canceled" }
|
||
|
||
# handle errors
|
||
# we try to undo all completed operations and trigger a DB rollback
|
||
(q.completed + q.running).sort.reverse_each do |task|
|
||
begin
|
||
task.status = "rollbacked"
|
||
update_cache
|
||
execute({:action => task.action, :rollback => true})
|
||
rescue => e
|
||
# if the operation failed, we can just report upon it
|
||
failure _("Failed to perform rollback on %{task} - %{e}") % { :task => task.name, :e => e }
|
||
end
|
||
end
|
||
|
||
rollback
|
||
end
|
||
|
||
def execute opts = {}
|
||
obj, met = opts[:action]
|
||
rollback = opts[:rollback] || false
|
||
# at the moment, rollback are expected to replace set with del in the method name
|
||
if rollback
|
||
met = met.to_s
|
||
case met
|
||
when /set/
|
||
met.gsub!("set","del")
|
||
when /del/
|
||
met.gsub!("del","set")
|
||
else
|
||
raise "Dont know how to rollback #{met}"
|
||
end
|
||
met = met.to_sym
|
||
end
|
||
if obj.respond_to?(met,true)
|
||
return obj.send(met)
|
||
else
|
||
failure _("invalid method %s") % met
|
||
raise ::Foreman::Exception.new(N_("invalid method %s"), met)
|
||
end
|
||
end
|
||
|
||
# we keep the before update host object in order to compare changes
|
||
def setup_clone
|
||
return if new_record?
|
||
@old = dup
|
||
for key in (changed_attributes.keys - ["updated_at"])
|
||
@old.send "#{key}=", changed_attributes[key]
|
||
# At this point the old cached bindings may still be present so we force an AR association reload
|
||
# This logic may not work or be required if we switch to Rails 3
|
||
if (match = key.match(/^(.*)_id$/))
|
||
name = match[1].to_sym
|
||
next if name == :owner # This does not work for the owner association even from the console
|
||
self.send(name, true) if (send(name) and send(name).id != @attributes[key])
|
||
old.send(name, true) if (old.send(name) and old.send(name).id != old.attributes[key])
|
||
end
|
||
end
|
||
end
|
||
|
||
def orchestration_errors?
|
||
overwrite? ? errors.are_all_conflicts? : errors.empty?
|
||
end
|
||
|
||
def update_cache
|
||
Rails.cache.write(progress_report_id, (queue.all + post_queue.all).to_json, :expires_in => 5.minutes)
|
||
end
|
||
|
||
end
|
||
end
|
app/models/concerns/orchestration/compute.rb | ||
---|---|---|
module Orchestration::Compute
|
||
def self.included(base)
|
||
base.send :include, InstanceMethods
|
||
base.class_eval do
|
||
attr_accessor :compute_attributes, :vm, :provision_method
|
||
after_validation :validate_compute_provisioning, :queue_compute
|
||
before_destroy :queue_compute_destroy
|
||
end
|
||
end
|
||
|
||
module InstanceMethods
|
||
def compute?
|
||
compute_resource_id.present? and compute_attributes.present?
|
||
end
|
||
|
||
def compute_object
|
||
if uuid.present? and compute_resource_id.present?
|
||
compute_resource.find_vm_by_uuid(uuid) rescue nil
|
||
# we don't want the fact that we failed to fetch the information to break foreman
|
||
# this is mostly relevant when the orchestration had a failure, and later on in the ui we try to retrieve the server again.
|
||
# or when the server was removed not via foreman.
|
||
elsif compute_resource_id.present? && compute_attributes
|
||
compute_resource.new_vm compute_attributes
|
||
end
|
||
end
|
||
|
||
protected
|
||
def queue_compute
|
||
return unless compute? and errors.empty?
|
||
new_record? ? queue_compute_create : queue_compute_update
|
||
end
|
||
|
||
def queue_compute_create
|
||
queue.create(:name => _("Settings up compute instance %s") % self, :priority => 1,
|
||
:action => [self, :setCompute])
|
||
queue.create(:name => _("Acquiring IP address for %s") % self, :priority => 2,
|
||
:action => [self, :setComputeIP]) if compute_resource.provided_attributes.keys.include?(:ip)
|
||
queue.create(:name => _("Querying instance details for %s") % self, :priority => 3,
|
||
:action => [self, :setComputeDetails])
|
||
queue.create(:name => _("Power up compute instance %s") % self, :priority => 1000,
|
||
:action => [self, :setComputePowerUp]) if compute_attributes[:start] == '1'
|
||
end
|
||
|
||
def queue_compute_update
|
||
return unless compute_update_required?
|
||
logger.debug("Detected a change is required for Compute resource")
|
||
queue.create(:name => _("Compute resource update for %s") % old, :priority => 7,
|
||
:action => [self, :setComputeUpdate])
|
||
end
|
||
|
||
def queue_compute_destroy
|
||
return unless errors.empty? and compute_resource_id.present? and uuid
|
||
queue.create(:name => _("Removing compute instance %s") % self, :priority => 100,
|
||
:action => [self, :delCompute])
|
||
end
|
||
|
||
def setCompute
|
||
logger.info "Adding Compute instance for #{name}"
|
||
self.vm = compute_resource.create_vm compute_attributes.merge(:name => name)
|
||
rescue => e
|
||
failure _("Failed to create a compute %{compute_resource} instance %{name}: %{message}\n ") % { :compute_resource => compute_resource, :name => name, :message => e.message }, e.backtrace
|
||
end
|
||
|
||
def setComputeDetails
|
||
if vm
|
||
attrs = compute_resource.provided_attributes
|
||
normalize_addresses if attrs.keys.include?(:mac) or attrs.keys.include?(:ip)
|
||
|
||
attrs.each do |foreman_attr, fog_attr |
|
||
# we can't ensure uniqueness of #foreman_attr using normal rails validations as that gets in a later step in the process
|
||
# therefore we must validate its not used already in our db.
|
||
value = vm.send(fog_attr)
|
||
self.send("#{foreman_attr}=", value)
|
||
|
||
if value.blank? or (other_host = Host.send("find_by_#{foreman_attr}", value))
|
||
delCompute
|
||
return failure("#{foreman_attr} #{value} is already used by #{other_host}") if other_host
|
||
return failure("#{foreman_attr} value is blank!")
|
||
end
|
||
end
|
||
true
|
||
else
|
||
failure _("failed to save %s") % name
|
||
end
|
||
end
|
||
|
||
def delComputeDetails; end
|
||
|
||
def setComputeIP
|
||
attrs = compute_resource.provided_attributes
|
||
if attrs.keys.include?(:ip)
|
||
logger.info "waiting for instance to acquire ip address"
|
||
vm.wait_for { self.send(attrs[:ip]).present? }
|
||
end
|
||
rescue => e
|
||
failure _("Failed to get IP for %{name}: %{e}") % { :name => name, :e => e }, e.backtrace
|
||
end
|
||
|
||
def delComputeIP;end
|
||
|
||
def delCompute
|
||
logger.info "Removing Compute instance for #{name}"
|
||
compute_resource.destroy_vm uuid
|
||
rescue => e
|
||
failure _("Failed to destroy a compute %{compute_resource} instance %{name}: %{e}") % { :compute_resource => compute_resource, :name => name, :e => e }, e.backtrace
|
||
end
|
||
|
||
def setComputePowerUp
|
||
logger.info "Powering up Compute instance for #{name}"
|
||
compute_resource.start_vm uuid
|
||
rescue => e
|
||
failure _("Failed to power up a compute %{compute_resource} instance %{name}: %{e}") % { :compute_resource => compute_resource, :name => name, :e => e }, e.backtrace
|
||
end
|
||
|
||
def delComputePowerUp
|
||
logger.info "Powering down Compute instance for #{name}"
|
||
compute_resource.stop_vm uuid
|
||
rescue => e
|
||
failure _("Failed to stop compute %{compute_resource} instance %{name}: %{e}") % { :compute_resource => compute_resource, :name => name, :e => e }, e.backtrace
|
||
end
|
||
|
||
def setComputeUpdate
|
||
logger.info "Update Compute instance for #{name}"
|
||
compute_resource.save_vm uuid, compute_attributes
|
||
rescue => e
|
||
failure _("Failed to update a compute %{compute_resource} instance %{name}: %{e}") % { :compute_resource => compute_resource, :name => name, :e => e }, e.backtrace
|
||
end
|
||
|
||
def delComputeUpdate
|
||
logger.info "Undo Update Compute instance for #{name}"
|
||
compute_resource.save_vm uuid, old.compute_attributes
|
||
rescue => e
|
||
failure _("Failed to undo update compute %{compute_resource} instance %{name}: %{e}") % { :compute_resource => compute_resource, :name => name, :e => e }, e.backtrace
|
||
end
|
||
|
||
private
|
||
|
||
def compute_update_required?
|
||
return false unless compute_resource.supports_update?
|
||
old.compute_attributes = compute_resource.find_vm_by_uuid(uuid).attributes
|
||
compute_resource.update_required?(old.compute_attributes, compute_attributes.symbolize_keys)
|
||
end
|
||
|
||
def validate_compute_provisioning
|
||
return true if compute_attributes.nil?
|
||
image_uuid = compute_attributes[:image_id] || compute_attributes[:image_ref]
|
||
return true if image_uuid.blank?
|
||
img = Image.where(:uuid => image_uuid, :compute_resource_id => compute_resource_id).first
|
||
if img
|
||
self.image = img
|
||
else
|
||
failure(_("Selected image does not belong to %s") % compute_resource) and return false
|
||
end
|
||
end
|
||
|
||
end
|
||
end
|
app/models/concerns/orchestration/dhcp.rb | ||
---|---|---|
module Orchestration::DHCP
|
||
def self.included(base)
|
||
base.send :include, InstanceMethods
|
||
base.class_eval do
|
||
after_validation :queue_dhcp
|
||
before_destroy :queue_dhcp_destroy
|
||
validate :ip_belongs_to_subnet?
|
||
end
|
||
end
|
||
|
||
module InstanceMethods
|
||
|
||
def dhcp?
|
||
name.present? and ip.present? and !subnet.nil? and subnet.dhcp? and managed? and capabilities.include?(:build)
|
||
end
|
||
|
||
def dhcp_record
|
||
return unless dhcp? or @dhcp_record
|
||
@dhcp_record ||= jumpstart? ? Net::DHCP::SparcRecord.new(dhcp_attrs) : Net::DHCP::Record.new(dhcp_attrs)
|
||
end
|
||
|
||
protected
|
||
|
||
def set_dhcp
|
||
dhcp_record.create
|
||
end
|
||
|
||
def set_dhcp_conflicts
|
||
dhcp_record.conflicts.each{|conflict| conflict.create}
|
||
end
|
||
|
||
def del_dhcp
|
||
dhcp_record.destroy
|
||
end
|
||
|
||
def del_dhcp_conflicts
|
||
dhcp_record.conflicts.each{|conflict| conflict.destroy}
|
||
end
|
||
|
||
# where are we booting from
|
||
def boot_server
|
||
# if we don't manage tftp at all, we dont create a next-server entry.
|
||
return unless tftp?
|
||
|
||
# first try to ask our TFTP server for its boot server
|
||
bs = tftp.bootServer
|
||
# if that failed, trying to guess out tftp next server based on the smart proxy hostname
|
||
bs ||= URI.parse(subnet.tftp.url).host
|
||
# now convert it into an ip address (see http://theforeman.org/issues/show/1381)
|
||
return to_ip_address(bs) if bs.present?
|
||
|
||
failure _("Unable to determine the host's boot server. The DHCP smart proxy failed to provide this information and this subnet is not provided with TFTP services.")
|
||
rescue => e
|
||
failure _("failed to detect boot server: %s") % e
|
||
end
|
||
|
||
private
|
||
|
||
# returns a hash of dhcp record settings
|
||
def dhcp_attrs
|
||
return unless dhcp?
|
||
dhcp_attr = { :name => name, :filename => operatingsystem.boot_filename(self),
|
||
:ip => ip, :mac => mac, :hostname => name, :proxy => subnet.dhcp_proxy,
|
||
:network => subnet.network, :nextServer => boot_server }
|
||
|
||
if jumpstart?
|
||
jumpstart_arguments = os.jumpstart_params self, model.vendor_class
|
||
dhcp_attr.merge! jumpstart_arguments unless jumpstart_arguments.empty?
|
||
end
|
||
dhcp_attr
|
||
end
|
||
|
||
def queue_dhcp
|
||
return unless (dhcp? or (old and old.dhcp?)) and orchestration_errors?
|
||
queue_remove_dhcp_conflicts if dhcp_conflict_detected?
|
||
new_record? ? queue_dhcp_create : queue_dhcp_update
|
||
end
|
||
|
||
def queue_dhcp_create
|
||
logger.debug "Scheduling new DHCP reservations for #{self}"
|
||
queue.create(:name => _("Create DHCP Settings for %s") % self, :priority => 10,
|
||
:action => [self, :set_dhcp]) if dhcp?
|
||
|
||
end
|
||
|
||
def queue_dhcp_update
|
||
if dhcp_update_required?
|
||
logger.debug("Detected a changed required for DHCP record")
|
||
queue.create(:name => _("Remove DHCP Settings for %s") % old, :priority => 5,
|
||
:action => [old, :del_dhcp]) if old.dhcp?
|
||
queue.create(:name => _("Create DHCP Settings for %s") % self, :priority => 9,
|
||
:action => [self, :set_dhcp]) if dhcp?
|
||
end
|
||
end
|
||
|
||
# do we need to update our dhcp reservations
|
||
def dhcp_update_required?
|
||
# IP Address / name changed
|
||
return true if ((old.ip != ip) or (old.name != name) or (old.mac != mac) or (old.subnet != subnet))
|
||
# Handle jumpstart
|
||
#TODO, abstract this way once interfaces are fully used
|
||
if self.kind_of?(Host::Base) and jumpstart?
|
||
if !old.build? or (old.medium != medium or old.arch != arch) or
|
||
(os and old.os and (old.os.name != os.name or old.os != os))
|
||
return true
|
||
end
|
||
end
|
||
false
|
||
end
|
||
|
||
def queue_dhcp_destroy
|
||
return unless dhcp? and errors.empty?
|
||
queue.create(:name => _("Remove DHCP Settings for %s") % self, :priority => 5,
|
||
:action => [self, :del_dhcp])
|
||
true
|
||
end
|
||
|
||
def queue_remove_dhcp_conflicts
|
||
return unless dhcp? and errors.any? and errors.are_all_conflicts?
|
||
return unless overwrite?
|
||
logger.debug "Scheduling DHCP conflicts removal"
|
||
queue.create(:name => _("DHCP conflicts removal for %s") % self, :priority => 5,
|
||
:action => [self, :del_dhcp_conflicts]) if dhcp_record and dhcp_record.conflicting?
|
||
end
|
||
|
||
def ip_belongs_to_subnet?
|
||
return if subnet.nil? or ip.nil?
|
||
return unless dhcp?
|
||
unless subnet.contains? ip
|
||
errors.add(:ip, _("Does not match selected Subnet"))
|
||
return false
|
||
end
|
||
rescue
|
||
# probably an invalid ip / subnet were entered
|
||
# we let other validations handle that
|
||
end
|
||
|
||
def dhcp_conflict_detected?
|
||
# we can't do any dhcp based validations when our MAC address is defined afterwards (e.g. in vm creation)
|
||
return false if mac.blank? or name.blank?
|
||
|
||
# This is an expensive operation and we will only do it if the DNS validation failed. This will ensure
|
||
# that we report on both DNS and DHCP conflicts when we offer to remove collisions. It retrieves and
|
||
# caches the conflicting records so we must always do it when overwriting
|
||
return false unless (errors.any? and errors.are_all_conflicts?) or overwrite?
|
||
|
||
return false unless dhcp?
|
||
status = true
|
||
status = failure(_("DHCP records %s already exists") % dhcp_record.conflicts.to_sentence, nil, :conflict) if dhcp_record and dhcp_record.conflicting?
|
||
overwrite? ? errors.are_all_conflicts? : status
|
||
end
|
||
|
||
end
|
||
end
|
app/models/concerns/orchestration/dns.rb | ||
---|---|---|
module Orchestration::DNS
|
||
def self.included(base)
|
||
base.send :include, InstanceMethods
|
||
base.class_eval do
|
||
after_validation :dns_conflict_detected?, :queue_dns
|
||
before_destroy :queue_dns_destroy
|
||
end
|
||
end
|
||
|
||
module InstanceMethods
|
||
|
||
def dns?
|
||
name.present? and ip_available? and !domain.nil? and !domain.proxy.nil? and managed?
|
||
end
|
||
|
||
def reverse_dns?
|
||
name.present? and ip_available? and !subnet.nil? and subnet.dns? and managed?
|
||
end
|
||
|
||
def dns_a_record
|
||
return unless dns? or @dns_a_record
|
||
@dns_a_record ||= Net::DNS::ARecord.new dns_record_attrs
|
||
end
|
||
|
||
def dns_ptr_record
|
||
return unless reverse_dns? or @dns_ptr_record
|
||
@dns_ptr_record ||= Net::DNS::PTRRecord.new reverse_dns_record_attrs
|
||
end
|
||
|
||
protected
|
||
|
||
def set_dns_a_record
|
||
dns_a_record.create
|
||
end
|
||
|
||
def set_conflicting_dns_a_record
|
||
dns_a_record.conflicts.each { |c| c.create }
|
||
end
|
||
|
||
def set_dns_ptr_record
|
||
dns_ptr_record.create
|
||
end
|
||
|
||
def set_conflicting_dns_ptr_record
|
||
dns_ptr_record.conflicts.each { |c| c.create }
|
||
end
|
||
|
||
def del_dns_a_record
|
||
dns_a_record.destroy
|
||
end
|
||
|
||
def del_conflicting_dns_a_record
|
||
dns_a_record.conflicts.each { |c| c.destroy }
|
||
end
|
||
|
||
def del_dns_ptr_record
|
||
dns_ptr_record.destroy
|
||
end
|
||
|
||
def del_conflicting_dns_ptr_record
|
||
dns_ptr_record.conflicts.each { |c| c.destroy }
|
||
end
|
||
|
||
private
|
||
|
||
def dns_record_attrs
|
||
{ :hostname => name, :ip => ip, :resolver => domain.resolver, :proxy => domain.proxy }
|
||
end
|
||
|
||
def reverse_dns_record_attrs
|
||
{ :hostname => name, :ip => ip, :proxy => subnet.dns_proxy }
|
||
end
|
||
|
||
def queue_dns
|
||
return unless (dns? or reverse_dns?) and errors.empty?
|
||
queue_remove_dns_conflicts if overwrite?
|
||
new_record? ? queue_dns_create : queue_dns_update
|
||
end
|
||
|
||
def queue_dns_create
|
||
logger.debug "Scheduling new DNS entries"
|
||
queue.create(:name => _("Create DNS record for %s") % self, :priority => 10,
|
||
:action => [self, :set_dns_a_record]) if dns?
|
||
queue.create(:name => _("Create Reverse DNS record for %s") % self, :priority => 10,
|
||
:action => [self, :set_dns_ptr_record]) if reverse_dns?
|
||
end
|
||
|
||
def queue_dns_update
|
||
if old.ip != ip or old.name != name
|
||
queue.create(:name => _("Remove DNS record for %s") % old, :priority => 9,
|
||
:action => [old, :del_dns_a_record]) if old.dns?
|
||
queue.create(:name => _("Remove Reverse DNS record for %s") % old, :priority => 9,
|
||
:action => [old, :del_dns_ptr_record]) if old.reverse_dns?
|
||
queue_dns_create
|
||
end
|
||
end
|
||
|
||
def queue_dns_destroy
|
||
return unless errors.empty?
|
||
queue.create(:name => _("Remove DNS record for %s") % self, :priority => 1,
|
||
:action => [self, :del_dns_a_record]) if dns?
|
||
queue.create(:name => _("Remove Reverse DNS record for %s") % self, :priority => 1,
|
||
:action => [self, :del_dns_ptr_record]) if reverse_dns?
|
||
end
|
||
|
||
def queue_remove_dns_conflicts
|
||
return unless errors.empty?
|
||
return unless overwrite?
|
||
logger.debug "Scheduling DNS conflict removal"
|
||
queue.create(:name => _("Remove conflicting DNS record for %s") % self, :priority => 0,
|
||
:action => [self, :del_conflicting_dns_a_record]) if dns? and dns_a_record and dns_a_record.conflicting?
|
||
queue.create(:name => _("Remove conflicting Reverse DNS record for %s") % self, :priority => 0,
|
||
:action => [self, :del_conflicting_dns_ptr_record]) if reverse_dns? and dns_ptr_record and dns_ptr_record.conflicting?
|
||
end
|
||
|
||
def dns_conflict_detected?
|
||
return false if ip.blank? or name.blank?
|
||
# can't validate anything if dont have an ip-address yet
|
||
return false unless require_ip_validation?
|
||
# we should only alert on conflicts if overwrite mode is off
|
||
return false if overwrite?
|
||
|
||
status = true
|
||
status = failure(_("DNS A Records %s already exists") % dns_a_record.conflicts.to_sentence, nil, :conflict) if dns? and dns_a_record and dns_a_record.conflicting?
|
||
status &= failure(_("DNS PTR Records %s already exists") % dns_ptr_record.conflicts.to_sentence, nil, :conflict) if reverse_dns? and dns_ptr_record and dns_ptr_record.conflicting?
|
||
status
|
||
end
|
||
|
||
def ip_available?
|
||
ip.present? || (capabilities.include?(:image) && compute_resource.provided_attributes.keys.include?(:ip))
|
||
end
|
||
|
||
end
|
||
end
|
app/models/concerns/orchestration/puppetca.rb | ||
---|---|---|
module Orchestration::Puppetca
|
||
def self.included(base)
|
||
base.send :include, InstanceMethods
|
||
base.class_eval do
|
||
attr_reader :puppetca
|
||
after_validation :initialize_puppetca, :queue_puppetca
|
||
before_destroy :initialize_puppetca, :queue_puppetca_destroy unless Rails.env == "test"
|
||
end
|
||
end
|
||
|
||
module InstanceMethods
|
||
|
||
protected
|
||
def initialize_puppetca
|
||
return unless puppetca?
|
||
return unless Setting[:manage_puppetca]
|
||
@puppetca = ProxyAPI::Puppetca.new :url => puppet_ca_proxy.url
|
||
true
|
||
rescue => e
|
||
failure _("Failed to initialize the PuppetCA proxy: %s") % e
|
||
end
|
||
|
||
# Removes the host's puppet certificate from the puppetmaster's CA
|
||
def delCertificate
|
||
logger.info "Remove puppet certificate for #{name}"
|
||
puppetca.del_certificate certname
|
||
rescue => e
|
||
failure _("Failed to remove %{name}'s puppet certificate: %{e}") % { :name => name, :e => proxy_error(e) }
|
||
end
|
||
|
||
# Empty method for rollbacks - maybe in the future we would support creating the certificates directly
|
||
def setCertificate; end
|
||
|
||
# Adds the host's name to the autosign.conf file
|
||
def setAutosign
|
||
logger.info "Adding autosign entry for #{name}"
|
||
puppetca.set_autosign certname
|
||
rescue => e
|
||
failure _("Failed to add %{name} to autosign file: %{e}") % { :name => name, :e => proxy_error(e) }
|
||
end
|
||
|
||
# Removes the host's name from the autosign.conf file
|
||
def delAutosign
|
||
logger.info "Delete the autosign entry for #{name}"
|
||
puppetca.del_autosign certname
|
||
rescue => e
|
||
failure _("Failed to remove %{self} from the autosign file: %{e}") % { :self => self, :e => proxy_error(e) }
|
||
end
|
||
|
||
private
|
||
|
||
def queue_puppetca
|
||
return unless puppetca? and errors.empty?
|
||
return unless Setting[:manage_puppetca]
|
||
new_record? ? queue_puppetca_create : queue_puppetca_update
|
||
end
|
||
|
||
# we don't perform any actions upon create
|
||
# PuppetCA is set only when a provisioning script (such as a kickstart) is being requested.
|
||
def queue_puppetca_create; end
|
||
|
||
def queue_puppetca_update
|
||
# Host has been built --> remove auto sign
|
||
if old.build? and !build?
|
||
queue.create(:name => _("Delete autosign entry for %s") % self, :priority => 50,
|
||
:action => [self, :delAutosign])
|
||
end
|
||
end
|
||
|
||
def queue_puppetca_destroy
|
||
return unless puppetca? and errors.empty?
|
||
return unless Setting[:manage_puppetca]
|
||
queue.create(:name => _("Delete PuppetCA certificates for %s") % self, :priority => 50,
|
||
:action => [self, :delCertificate])
|
||
queue.create(:name => _("Delete PuppetCA certificates for %s") % self, :priority => 55,
|
||
:action => [self, :delAutosign])
|
||
end
|
||
end
|
||
end
|
app/models/concerns/orchestration/ssh_provision.rb | ||
---|---|---|
module Orchestration::SSHProvision
|
||
def self.included(base)
|
||
base.send :include, InstanceMethods
|
||
base.class_eval do
|
||
after_validation :validate_ssh_provisioning, :queue_ssh_provision
|
||
attr_accessor :template_file, :client
|
Also available in: Unified diff
fixes #2411 - move files in /models to /concerns, /services, /mailers, /observers