Skip to content

Commit

Permalink
fixes #16725 - add plugin extension point for smart proxies
Browse files Browse the repository at this point in the history
  • Loading branch information
timogoebel committed Sep 30, 2016
1 parent ad94991 commit bf610a4
Show file tree
Hide file tree
Showing 34 changed files with 347 additions and 176 deletions.
4 changes: 3 additions & 1 deletion app/controllers/api/v2/domains_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ def show
param :domain, Hash, :required => true, :action_aware => true do
param :name, String, :required => true, :desc => N_("The full DNS domain name")
param :fullname, String, :required => false, :allow_nil => true, :desc => N_("Description of the domain")
param :dns_id, :number, :required => false, :allow_nil => true, :desc => N_("DNS proxy to use within this domain")
Domain.smart_proxies.each do |name, options|
param :"#{name}_id", :number, :required => false, :allow_nil => true, :desc => options[:api_description]
end
param :domain_parameters_attributes, Array, :required => false, :desc => N_("Array of parameters (name, value)")
param_group :taxonomies, ::Api::V2::BaseController
end
Expand Down
5 changes: 3 additions & 2 deletions app/controllers/api/v2/hostgroups_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ def show
param :pxe_loader, Operatingsystem.all_loaders, :desc => N_("DHCP filename option (Grub2/PXELinux by default)")
param :medium_id, :number, :desc => N_('Media ID')
param :ptable_id, :number, :desc => N_('Partition table ID')
param :puppet_ca_proxy_id, :number, :desc => N_('Puppet CA proxy ID')
param :subnet_id, :number, :desc => N_('Subnet ID')
param :domain_id, :number, :desc => N_('Domain ID')
param :realm_id, :number, :desc => N_('Realm ID')
param :puppet_proxy_id, :number, :desc => N_('Puppet proxy ID')
Hostgroup.smart_proxies.each do |name, options|
param :"#{name}_id", :number, :desc => options[:api_description]
end
param :root_pass, String, :desc => N_('Root password on provisioned hosts')
param_group :taxonomies, ::Api::V2::BaseController
end
Expand Down
5 changes: 3 additions & 2 deletions app/controllers/api/v2/hosts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ def show
param :architecture_id, :number, :desc => N_("required if host is managed and value is not inherited from host group")
param :domain_id, :number, :desc => N_("required if host is managed and value is not inherited from host group")
param :realm_id, :number
param :puppet_proxy_id, :number
Host.smart_proxies.each do |name, options|
param :"#{name}_id", :number, :desc => options[:api_description]
end
param :puppetclass_ids, Array
param :operatingsystem_id, String, :desc => N_("required if host is managed and value is not inherited from host group")
param :medium_id, String, :desc => N_("required if not imaged based provisioning and host is managed and value is not inherited from host group")
Expand All @@ -66,7 +68,6 @@ def show
param :hostgroup_id, :number
param :owner_id, :number
param :owner_type, Host::Base::OWNER_TYPES, :desc => N_("Host's owner type")
param :puppet_ca_proxy_id, :number
param :image_id, :number
param :host_parameters_attributes, Array, :desc => N_("Host's parameters (array or indexed hash)") do
param :name, String, :desc => N_("Name of the parameter"), :required => true
Expand Down
4 changes: 3 additions & 1 deletion app/controllers/api/v2/realms_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def show
def_param_group :realm do
param :realm, Hash, :required => true, :action_aware => true do
param :name, String, :required => true, :desc => N_("The realm name, e.g. EXAMPLE.COM")
param :realm_proxy_id, :number, :required => true, :allow_nil => true, :desc => N_("Proxy to use for this realm")
Realm.smart_proxies.each do |name, options|
param :"#{name}_id", :number, :required => true, :allow_nil => true, :desc => options[:api_description]
end
param :realm_type, String, :required => true, :desc => N_("Realm type, e.g. FreeIPA or Active Directory")
param_group :taxonomies, ::Api::V2::BaseController
end
Expand Down
6 changes: 3 additions & 3 deletions app/controllers/api/v2/subnets_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ def show
param :to, String, :desc => N_("Ending IP Address for IP auto suggestion")
param :vlanid, String, :desc => N_("VLAN ID for this subnet")
param :domain_ids, Array, :desc => N_("Domains in which this subnet is part")
param :dhcp_id, :number, :desc => N_("DHCP Proxy to use within this subnet")
param :tftp_id, :number, :desc => N_("TFTP Proxy to use within this subnet")
param :dns_id, :number, :desc => N_("DNS Proxy to use within this subnet")
Subnet.smart_proxies.each do |name, options|
param :"#{name}_id", :number, :desc => options[:api_description]
end
param :boot_mode, String, :desc => N_('Default boot mode for interfaces assigned to this subnet, valid values are "Static", "DHCP"')
param :subnet_parameters_attributes, Array, :required => false, :desc => N_("Array of parameters (name, value)")
param_group :taxonomies, ::Api::V2::BaseController
Expand Down
39 changes: 39 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,43 @@ def webpack_dev_server
return unless Rails.configuration.webpack.dev_server.enabled
javascript_include_tag "#{@dev_server}/webpack-dev-server.js"
end

def accessible_resource_records(resource, order = :name)
klass = resource.to_s.classify.constantize
klass = klass.with_taxonomy_scope_override(@location, @organization) if klass.include? Taxonomix
klass.authorized.reorder(order)
end

def accessible_resource(obj, resource, order = :name)
list = accessible_resource_records(resource, order).to_a
# we need to allow the current value even if it was filtered
current = obj.public_send(resource) if obj.respond_to?(resource)
list |= [current] if current.present?
list
end

def accessible_related_resource(obj, relation, opts = {})
return [] if obj.blank?
order = opts.fetch(:order, :name)
where = opts.fetch(:where, nil)
related = obj.public_send(relation)
related = related.with_taxonomy_scope_override(@location, @organization) if obj.class.reflect_on_association(relation).klass.include?(Taxonomix)
related.authorized.where(where).reorder(order)
end

def explicit_value?(field)
return true if params[:action] == 'clone'
return false unless params[:host]
!!params[:host][field]
end

def user_set?(field)
# if the host has no hostgroup
return true unless @host && @host.hostgroup
# when editing a host, the values are specified explicitly
return true if params[:action] == 'edit'
return true if params[:action] == 'clone'
# check if the user set the field explicitly despite setting a hostgroup.
params[:host] && params[:host][:hostgroup_id] && params[:host][field]
end
end
74 changes: 0 additions & 74 deletions app/helpers/hosts_and_hostgroups_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,6 @@ def model_name(host)
name
end

def accessible_resource_records(resource, order = :name)
klass = resource.to_s.classify.constantize
klass = klass.with_taxonomy_scope_override(@location, @organization) if klass.include? Taxonomix
klass.authorized.reorder(order)
end

def accessible_resource(obj, resource, order = :name)
list = accessible_resource_records(resource, order).to_a
# we need to allow the current value even if it was filtered
current = obj.public_send(resource) if obj.respond_to?(resource)
list |= [current] if current.present?
list
end

def accessible_related_resource(obj, relation, opts = {})
return [] if obj.blank?
order = opts.fetch(:order, :name)
where = opts.fetch(:where, nil)
related = obj.public_send(relation)
related = related.with_taxonomy_scope_override(@location, @organization) if obj.class.reflect_on_association(relation).klass.include?(Taxonomix)
related.authorized.where(where).reorder(order)
end

def parent_classes(obj)
return obj.hostgroup.classes if obj.is_a?(Host::Base) && obj.hostgroup
return obj.is_root? ? [] : obj.parent.classes if obj.is_a?(Hostgroup)
Expand Down Expand Up @@ -66,43 +43,8 @@ def os_ptable
accessible_related_resource(@operatingsystem, :ptables)
end

def puppet_master_fields(f, can_override = false, override = false)
"#{puppet_ca(f, can_override, override)} #{puppet_master(f, can_override, override)}".html_safe
end

INHERIT_TEXT = N_("inherit")

def puppet_ca(f, can_override, override)
# Don't show this if we have no CA proxies, otherwise always include blank
# so the user can choose not to sign the puppet cert on this host
proxies = accessible_puppet_ca_proxies(f.object)
return unless proxies.present?
select_f f, :puppet_ca_proxy_id, proxies, :id, :name,
{ :include_blank => blank_or_inherit_f(f, :puppet_ca_proxy),
:disable_button => can_override ? _(INHERIT_TEXT) : nil,
:disable_button_enabled => override && !explicit_value?(:puppet_ca_proxy_id),
:user_set => user_set?(:puppet_ca_proxy_id)
},
{ :label => _("Puppet CA"),
:help_inline => _("Use this puppet server as a CA server") }
end

def puppet_master(f, can_override, override)
# Don't show this if we have no Puppet proxies, otherwise always include blank
# so the user can choose not to use puppet on this host
proxies = accessible_puppet_proxies(f.object)
return unless proxies.present?
select_f f, :puppet_proxy_id, proxies, :id, :name,
{ :include_blank => blank_or_inherit_f(f, :puppet_proxy),
:disable_button => can_override ? _(INHERIT_TEXT) : nil,
:disable_button_enabled => override && !explicit_value?(:puppet_proxy_id),
:user_set => user_set?(:puppet_proxy_id)

},
{ :label => _("Puppet Master"),
:help_inline => _("Use this puppet server as an initial Puppet Server or to execute puppet runs") }
end

def realm_field(f, can_override = false, override = false)
# Don't show this if we have no Realms, otherwise always include blank
# so the user can choose not to use a Realm on this host
Expand Down Expand Up @@ -185,22 +127,6 @@ def interesting_puppetclasses(obj)
classes.where(:id => klasses)
end

def explicit_value?(field)
return true if params[:action] == 'clone'
return false unless params[:host]
!!params[:host][field]
end

def user_set?(field)
# if the host has no hostgroup
return true unless @host && @host.hostgroup
# when editing a host, the values are specified explicitly
return true if params[:action] == 'edit'
return true if params[:action] == 'clone'
# check if the user set the field explicitly despite setting a hostgroup.
params[:host] && params[:host][:hostgroup_id] && params[:host][field]
end

def puppetclasses_tab(puppetclasses_receiver)
return unless accessible_puppet_proxies(puppetclasses_receiver).present?
content_tag(:div, :class => 'tab-pane', :id => 'puppet_klasses') do
Expand Down
42 changes: 42 additions & 0 deletions app/helpers/shared_smart_proxies_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module SharedSmartProxiesHelper
def smart_proxy_fields(f, options = {})
object = options.fetch(:object, f.object)

safe_join(object.smart_proxies.map do |proxy_name, proxy_options|
smart_proxy_select_f(f, proxy_name, options.merge(proxy_options))
end)
end

INHERIT_TEXT = N_("inherit")

def smart_proxy_select_f(f, resource, options)
required = options.fetch(:required, false)
hidden = options[:if].present? && !options[:if].call(f.object)
can_override = options.fetch(:can_override, false)
override = options.fetch(:override, false)
blank = options.fetch(:blank, blank_or_inherit_f(f, resource))

proxies = accessible_smart_proxies(f.object, resource, options[:feature])
return if !required && !proxies.present?

select_options = {
:disable_button => can_override ? _(INHERIT_TEXT) : nil,
:disable_button_enabled => override && !explicit_value?(:"#{resource}_id"),
:user_set => user_set?(:"#{resource}_id")
}
select_options[:include_blank] = blank unless required

select_f f, :"#{resource}_id", proxies, :id, :name,
select_options,
:label => _(options[:label]),
:help_inline => _(options[:description]),
:wrapper_class => "form-group #{'hide' if hidden}"
end

def accessible_smart_proxies(obj, resource, feature)
list = accessible_resource_records(:smart_proxy).with_features(feature).to_a
current = obj.public_send(resource) if obj.respond_to?(resource)
list |= [current] if current.present?
list
end
end
42 changes: 42 additions & 0 deletions app/models/concerns/belongs_to_proxies.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module BelongsToProxies
extend ActiveSupport::Concern

included do
class_attribute :smart_proxies_from_model
register_smart_proxies_from_plugins
end

delegate :smart_proxies, :to => :class

module ClassMethods
def belongs_to_proxy(name, options)
register_smart_proxy(name, options)
self.smart_proxies_from_model = (smart_proxies_from_model || {}).merge(name => options)
end

def smart_proxies
(smart_proxies_from_model || {}).merge(smart_proxies_from_plugins)
end

def register_smart_proxy(name, options)
belongs_to name, :class_name => 'SmartProxy'
validates name, :proxy_features => { :feature => options[:feature] }
end

def register_smart_proxies_from_plugins
smart_proxies_from_plugins.each do |name, options|
register_smart_proxy(name, options)
end
end

private

def smart_proxies_from_model
@smart_proxies_from_model ||= {}
end

def smart_proxies_from_plugins
Foreman::Plugin.all.map {|plugin| plugin.smart_proxies(self) }.inject({}, :merge)
end
end
end
17 changes: 13 additions & 4 deletions app/models/concerns/host_common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@
# mostly for template rendering consistency
module HostCommon
extend ActiveSupport::Concern
include BelongsToProxies

included do
belongs_to_proxy :puppet_proxy,
:feature => N_('Puppet'),
:label => N_('Puppet Master'),
:description => N_('Use this puppet server as an initial Puppet Server or to execute puppet runs'),
:api_description => N_('Puppet proxy ID')

belongs_to_proxy :puppet_ca_proxy,
:feature => 'Puppet CA',
:label => N_('Puppet CA'),
:description => N_('Use this puppet server as a CA server'),
:api_description => N_('Puppet CA proxy ID')

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 :realm
belongs_to :compute_profile
validates :puppet_ca_proxy, :proxy_features => { :feature => "Puppet CA", :message => N_("does not have the Puppet CA feature") }
validates :puppet_proxy, :proxy_features => { :feature => "Puppet", :message => N_("does not have the Puppet feature") }

before_save :check_puppet_ca_proxy_is_required?, :crypt_root_pass
has_many :host_config_groups, :as => :host
Expand Down
10 changes: 8 additions & 2 deletions app/models/domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Domain < ActiveRecord::Base
include Taxonomix
include StripLeadingAndTrailingDot
include Parameterizable::ByIdName
include BelongsToProxies

audited
validates_lengths_from_database
Expand All @@ -15,7 +16,13 @@ class Domain < ActiveRecord::Base
before_destroy EnsureNotUsedBy.new(:interfaces, :hostgroups, :subnets)
has_many :subnet_domains, :dependent => :destroy, :inverse_of => :domain
has_many :subnets, :through => :subnet_domains
belongs_to :dns, :class_name => "SmartProxy"

belongs_to_proxy :dns,
:feature => 'DNS',
:label => N_('DNS Proxy'),
:description => N_('DNS proxy to use within this domain for managing A records, note that PTR records are managed via Subnet DNS proxy'),
:api_description => N_('DNS proxy ID to use within this domain')

has_many :domain_parameters, :dependent => :destroy, :foreign_key => :reference_id, :inverse_of => :domain
has_many :parameters, :dependent => :destroy, :foreign_key => :reference_id, :class_name => "DomainParameter"
has_many :interfaces, :class_name => 'Nic::Base'
Expand All @@ -27,7 +34,6 @@ class Domain < ActiveRecord::Base
include ParameterValidators
validates :name, :presence => true, :uniqueness => true
validates :fullname, :uniqueness => true, :allow_blank => true, :allow_nil => true
validates :dns, :proxy_features => { :feature => "DNS", :message => N_('does not have the DNS feature') }

scoped_search :on => [:name, :fullname], :complete_value => true
scoped_search :in => :domain_parameters, :on => :value, :on_key=> :name, :complete_value => true, :only_explicit => true, :rename => :params
Expand Down
10 changes: 8 additions & 2 deletions app/models/realm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ class Realm < ActiveRecord::Base
extend FriendlyId
friendly_id :name
include Taxonomix
include BelongsToProxies

TYPES = ["FreeIPA", "Active Directory"]

validates_lengths_from_database
audited
before_destroy EnsureNotUsedBy.new(:hosts, :hostgroups)

belongs_to :realm_proxy, :class_name => "SmartProxy"
belongs_to_proxy :realm_proxy,
:feature => 'Realm',
:label => N_('Realm proxy'),
:description => N_('Realm proxy to use within this realm'),
:api_description => N_('Proxy ID to use within this realm'),
:required => true

has_many_hosts
has_many :hostgroups
validates :realm_proxy, :proxy_features => { :feature => "Realm", :message => N_('does not have the Realm feature') }

scoped_search :on => :name, :complete_value => true
scoped_search :on => :realm_type, :complete_value => true, :rename => :type
Expand Down
Loading

0 comments on commit bf610a4

Please sign in to comment.