Project

General

Profile

Download (7.83 KB) Statistics
| Branch: | Tag: | Revision:
class SmartProxy < ApplicationRecord
audited
include Authorizable
extend FriendlyId
friendly_id :name
include Taxonomix
include Parameterizable::ByIdName

validates_lengths_from_database
before_destroy EnsureNotUsedBy.new(:hosts, :hostgroups, :subnets, :domains, [:puppet_ca_hosts, :hosts], [:puppet_ca_hostgroups, :hostgroups], :realms)
# TODO check if there is a way to look into the tftp_id too
# maybe with a predefined sql
has_many :smart_proxy_features, :dependent => :destroy
has_many :features, :through => :smart_proxy_features
has_many :subnets, :foreign_key => 'dhcp_id'
has_many :domains, :foreign_key => 'dns_id'
has_many_hosts :foreign_key => 'puppet_proxy_id'
has_many :hostgroups, :foreign_key => 'puppet_proxy_id'
has_many :puppet_ca_hosts, :class_name => 'Host::Managed', :foreign_key => 'puppet_ca_proxy_id'
has_many :puppet_ca_hostgroups, :class_name => 'Hostgroup', :foreign_key => 'puppet_ca_proxy_id'
has_many :realms, :foreign_key => 'realm_proxy_id'
validates :name, :uniqueness => true, :presence => true
validates :url, :presence => true, :url_schema => ['http', 'https'],
:uniqueness => { :message => N_('Only one declaration of a proxy is allowed') }
has_many :infrastructure_host_facets, :class_name => '::HostFacets::InfrastructureFacet', :dependent => :nullify
has_many :smart_proxy_hosts, :through => :infrastructure_host_facets, :source => :host

# There should be no problem with associating features before the proxy is saved as the whole operation is in a transaction
before_save :sanitize_url, :associate_features

scoped_search :on => :id, :complete_enabled => false, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER
scoped_search :on => :name, :complete_value => :true
scoped_search :on => :url, :complete_value => :true
scoped_search :relation => :features, :on => :name, :rename => :feature, :complete_value => :true

# with proc support, default_scope can no longer be chained
# include all default scoping here
default_scope lambda {
with_taxonomy_scope do
order('smart_proxies.name')
end
}

scope :with_features, ->(*feature_names) { where(:features => { :name => feature_names }).joins(:features) if feature_names.any? }

def hostname
URI(url).host
end

def to_s
hostname
end

def hosts_count
Host::Managed.search_for("smart_proxy = #{name}").count
end

def refresh
statuses.values.each { |status| status.revoke_cache! }
associate_features
errors
end

def ping
begin
reply = get_features
unless reply.is_a?(Hash)
logger.debug("Invalid response from proxy #{name}: Expected Hash of features, got #{reply}.")
errors.add(:base, _('An invalid response was received while requesting available features from this proxy'))
end
rescue => e
errors.add(:base, _('Unable to communicate with the proxy: %s') % e)
end
!errors.any?
end

def taxonomy_foreign_conditions
conditions = {}
if has_feature?('Puppet') && has_feature?('Puppet CA')
conditions = "puppet_proxy_id = #{id} OR puppet_ca_proxy_id = #{id}"
elsif has_feature?('Puppet')
conditions[:puppet_proxy_id] = id
elsif has_feature?('Puppet CA')
conditions[:puppet_ca_proxy_id] = id
end
conditions
end

def smart_proxy_feature_by_name(feature_name)
feature_id = Feature.find_by(:name => feature_name).try(:id)
# loop through the in memory object to work on unsaved objects
smart_proxy_features.find { |spf| spf.feature_id == feature_id }
end

def has_feature?(feature_name)
feature_ids = Feature.where(:name => feature_name).pluck(:id)
smart_proxy_features.any? { |proxy_feature| feature_ids.include?(proxy_feature.feature_id) }
end

def capabilities(feature)
smart_proxy_feature_by_name(feature).try(:capabilities)
end

def has_capability?(feature, capability)
capabilities(feature)&.include?(capability.to_s)
end

def setting(feature, setting)
smart_proxy_feature_by_name(feature).try(:settings).try(:[], setting)
end

def httpboot_http_port
setting(:HTTPBoot, 'http_port')
end

def httpboot_http_port!
httpboot_http_port || raise(::Foreman::Exception.new(N_("HTTP boot requires proxy with httpboot feature and http_port exposed setting")))
end

def httpboot_https_port
setting(:HTTPBoot, 'https_port')
end

def httpboot_https_port!
httpboot_https_port || raise(::Foreman::Exception.new(N_("HTTPS boot requires proxy with httpboot feature and https_port exposed setting")))
end

def statuses
return @statuses if @statuses
@statuses = {}
features.each do |feature|
name = feature.name.delete(' ')
if (status = ProxyStatus.find_status_by_humanized_name(name))
@statuses[name.downcase.to_sym] = status.new(self)
end
end
@statuses[:version] = ProxyStatus::Version.new(self)

@statuses
end

def feature_details
smart_proxy_features.includes(:feature).each_with_object({}) do |smart_proxy_feature, hash|
hash[smart_proxy_feature.feature.name] = smart_proxy_feature.details
end
end

private

def sanitize_url
self.url = url.chomp('/') unless url.empty?
end

def associate_features
begin
reply = get_features
unless reply.is_a?(Hash)
logger.debug("Invalid response from proxy #{name}: Expected Hash or Array of features, got #{reply}.")
errors.add(:base, _('An invalid response was received while requesting available features from this proxy'))
throw :abort
end

feature_name_map = Feature.name_map
valid_features = reply.select { |feature, options| feature_name_map.key?(feature) }

if valid_features.any?
SmartProxyFeature.import_features(self, valid_features)
else
smart_proxy_features.clear
if reply.any?
errors.add :base, _('Features "%s" in this proxy are not recognized by Foreman. '\
'If these features come from a Smart Proxy plugin, make sure Foreman has the plugin installed too.') % reply.keys.to_sentence
else
errors.add :base, _('No features found on this proxy, please make sure you enable at least one feature')
end
end
rescue => e
errors.add(:base, _('Unable to communicate with the proxy: %s') % e)
errors.add(:base, _('Please check the proxy is configured and running on the host.'))
end
throw :abort if smart_proxy_features.empty?
end

def get_features
begin
reply = ProxyAPI::V2::Features.new(:url => url).features.with_indifferent_access
reply.reject! { |name| reply[name]['state'] != 'running' }
rescue NotImplementedError
reply = ProxyAPI::Features.new(:url => url).features
end

if reply.is_a?(Array)
Hash[reply.collect { |f| [f, {}] }]
else
reply
end
end

apipie :class do
name 'Smart Proxy'
refs 'SmartProxy'
sections only: %w[all additional]
prop_group :basic_model_props, ApplicationRecord, meta: { friendly_name: 'Smart Proxy' }
property :hostname, String, desc: 'Returns name of the host with proxy'
property :httpboot_http_port, Integer, desc: 'Returns proxy port for HTTP boot'
property :httpboot_http_port!, Integer, desc: 'Same as httpboot_http_port, but raises Foreman::Exception if no port is set'
property :httpboot_https_port, Integer, desc: 'Returns proxy port for HTTPS boot'
property :httpboot_https_port!, Integer, desc: 'Same as httpboot_https_port, but raises Foreman::Exception if no port is set'
end
class Jail < ::Safemode::Jail
allow :id, :name, :hostname, :httpboot_http_port, :httpboot_https_port, :httpboot_http_port!, :httpboot_https_port!, :url
end
end
(48-48/69)