Project

General

Profile

« Previous | Next » 

Revision 611f5bff

Added by Amos Benari over 11 years ago

  • ID 611f5bff49c1f06ae9ad0dd42b7566df9a02af2f

Add organization and location to foreman.

This feature allows foreman to provide multi location, multi tenant and
multi organizations capablities.

the idea is that resources within foreman (e.g. hosts, subnets, users,
environments etc) can belong to one or more locations and organization,
effectivily hidding resources from users.

Organization may contain multiple locations, and Locations can belong
to multiple Organization, this is an extermily flexiable design,
however, it is up to the user to allocate the resources across the
organizations and locations.

When creating new hosts, the resources that can be consumed are only
resources in which exists in both the currently used organization and location.

fixes #1578
fixes #1593

View differences:

Gemfile
gem "audited-activerecord", "3.0.0.rc1"
gem "has_many_polymorphs", :git => "https://github.com/jystewart/has_many_polymorphs.git", :ref => '03429a61e511f394e9f96af0c8998268ca99d42b'
gem "will_paginate", "~> 3.0.2"
gem "ancestry", "~> 1.2.4"
gem "ancestry", "~> 1.3"
gem 'scoped_search', '>= 2.4'
gem 'net-ldap'
gem "safemode", "~> 1.0.1"
app/controllers/application_controller.rb
helper 'layout'
before_filter :require_ssl, :require_login
before_filter :require_mail
before_filter :session_expiry, :update_activity_time, :unless => proc {|c| c.remote_user_provided? || c.api_request? } if SETTINGS[:login]
before_filter :set_taxonomy, :require_mail
before_filter :welcome, :only => :index, :unless => :api_request?
before_filter :authorize
......
def welcome
@searchbar = true
klass = controller_name == "dashboard" ? "Host" : controller_name.camelize.singularize
eval "#{klass}" rescue nil # We must force an autoload of the model class
if eval "defined?(#{klass}) and #{klass}.respond_to?(:unconfigured?) and #{klass}.unconfigured?"
if (klass.constantize.first.nil? rescue false)
@searchbar = false
render :welcome rescue nil and return
end
......
# Force a user to login if authentication is enabled
# Sets User.current to the logged in user, or to admin if logins are not used
def require_login
unless session[:user] and (User.current = User.find(session[:user]))
unless session[:user] and (User.current = User.unscoped.find(session[:user]))
# User is not found or first login
if SETTINGS[:login]
# authentication is enabled
......
# If REMOTE_USER is provided by the web server then
# authenticate the user without using password.
if remote_user_provided?
user = User.find_by_login(@remote_user)
user = User.unscoped.find_by_login(@remote_user)
logger.warn("Failed REMOTE_USER authentication from #{request.remote_ip}") unless user
# Else, fall back to the standard authentication mechanism,
# only if it's an API request.
......
render :template => "common/500", :layout => !request.xhr?, :status => 500, :locals => { :exception => exception}
end
def set_taxonomy
return if User.current.nil?
if SETTINGS[:organizations_enabled]
orgs = Organization.my_organizations
Organization.current = if orgs.count == 1
orgs.first
elsif session[:org_id]
orgs.find(session[:org_id])
else
nil
end
end
if SETTINGS[:locations_enabled]
locations = Location.my_locations
Location.current = if locations.count == 1
locations.first
elsif session[:location_id]
locations.find(session[:location_id])
else
nil
end
end
end
end
app/controllers/fact_values_controller.rb
skip_before_filter :require_login, :only => :create
skip_before_filter :authorize, :only => :create
skip_before_filter :verify_authenticity_token, :only => :create
skip_before_filter :set_taxonomy, :only => :create
skip_before_filter :session_expiry, :update_activity_time, :only => :create
before_filter :set_admin_user, :only => :create
before_filter :setup_search_options, :only => :index
app/controllers/home_controller.rb
class HomeController < ApplicationController
skip_before_filter :require_login, :only => [:status]
skip_before_filter :authorize, :only => [:status]
skip_before_filter :authorize, :set_taxonomy, :only => [:status]
skip_before_filter :session_expiry, :update_activity_time, :only => :status
def settings
app/controllers/hosts_controller.rb
skip_before_filter :require_login, :only => ANONYMOUS_ACTIONS
skip_before_filter :require_ssl, :only => ANONYMOUS_ACTIONS
skip_before_filter :authorize, :only => ANONYMOUS_ACTIONS
skip_before_filter :set_taxonomy, :only => ANONYMOUS_ACTIONS
skip_before_filter :session_expiry, :update_activity_time, :only => ANONYMOUS_ACTIONS
before_filter :set_admin_user, :only => ANONYMOUS_ACTIONS
......
def update
forward_request_url
if @host.update_attributes(params[:host])
process_success :success_redirect => host_path(@host), :redirect_xhr => request.xhr?
else
load_vars_for_ajax
offer_to_overwrite_conflicts
process_error
Taxonomy.no_taxonomy_scope do
if @host.update_attributes(params[:host])
process_success :success_redirect => host_path(@host), :redirect_xhr => request.xhr?
else
load_vars_for_ajax
offer_to_overwrite_conflicts
process_error
end
end
end
......
def hostgroup_or_environment_selected
return head(:method_not_allowed) unless request.xhr?
@environment = Environment.find(params[:environment_id]) unless params[:environment_id].empty?
@hostgroup = Hostgroup.find(params[:hostgroup_id]) unless params[:hostgroup_id].empty?
@host = Host.find(params[:host_id]) if params[:host_id].to_i > 0
if @environment or @hostgroup
@host ||= Host.new
@host.hostgroup = @hostgroup if @hostgroup
@host.environment = @environment if @environment
render :partial => 'puppetclasses/class_selection', :locals => {:obj => (@host)}
else
head(:not_found)
@organization = params[:organization_id] ? Organization.find(params[:organization_id]) : nil
@location = params[:location_id] ? Location.find(params[:location_id]) : nil
Taxonomy.as_taxonomy @organization, @location do
@environment = Environment.find(params[:environment_id]) unless params[:environment_id].empty?
@hostgroup = Hostgroup.find(params[:hostgroup_id]) unless params[:hostgroup_id].empty?
@host = Host.find(params[:host_id]) if params[:host_id].to_i > 0
if @environment or @hostgroup
@host ||= Host.new
@host.hostgroup = @hostgroup if @hostgroup
@host.environment = @environment if @environment
render :partial => 'puppetclasses/class_selection', :locals => {:obj => (@host)}
else
head(:not_found)
end
end
end
......
@hostgroup = Hostgroup.find(params[:hostgroup_id]) if params[:hostgroup_id].to_i > 0
return head(:not_found) unless @hostgroup
@architecture = @hostgroup.architecture
@operatingsystem = @hostgroup.operatingsystem
@environment = @hostgroup.environment
@domain = @hostgroup.domain
@subnet = @hostgroup.subnet
@organization = params[:organization_id] ? Organization.find(params[:organization_id]) : nil
@location = params[:location_id] ? Location.find(params[:location_id]) : nil
Taxonomy.as_taxonomy @organization, @location do
@host = Host.new
@host.hostgroup = @hostgroup
@host.compute_resource_id = params[:compute_resource_id] if params[:compute_resource_id].present?
@host.set_hostgroup_defaults
@architecture = @hostgroup.architecture
@operatingsystem = @hostgroup.operatingsystem
@environment = @hostgroup.environment
@domain = @hostgroup.domain
@subnet = @hostgroup.subnet
@host = Host.new
@host.hostgroup = @hostgroup
@host.compute_resource_id = params[:compute_resource_id] if params[:compute_resource_id].present?
@host.set_hostgroup_defaults
render :update do |page|
[:environment_id, :puppet_ca_proxy_id, :puppet_proxy_id].each do |field|
page["*[id*=#{field}]"].val(@hostgroup.send(field)) if @hostgroup.send(field).present?
end
page['#puppet_klasses'].html(render(:partial => 'puppetclasses/class_selection', :locals => {:obj => @host})) if @environment
if SETTINGS[:unattended]
if @architecture
page['#os_select'].html(render(:partial => 'common/os_selection/architecture', :locals => {:item => @host}))
page['#*[id*=architecture_id]'].val(@architecture.id)
render :update do |page|
[:environment_id, :puppet_ca_proxy_id, :puppet_proxy_id].each do |field|
page["*[id*=#{field}]"].val(@hostgroup.send(field)) if @hostgroup.send(field).present?
end
page['#puppet_klasses'].html(render(:partial => 'puppetclasses/class_selection', :locals => {:obj => @host})) if @environment
page['#media_select'].html(render(:partial => 'common/os_selection/operatingsystem', :locals => {:item => @host})) if @operatingsystem
if SETTINGS[:unattended]
if @architecture
page['#os_select'].html(render(:partial => 'common/os_selection/architecture', :locals => {:item => @host}))
page['#*[id*=architecture_id]'].val(@architecture.id)
end
if @domain
page['*[id*=domain_id]'].val(@domain.id)
if @domain.subnets.any?
page['#subnet_select'].html(render(:partial => 'common/domain', :locals => {:item => @host}))
page['#host_subnet_id'].val(@subnet.id).change if @subnet
page['#sp_subnet'].html(render(:partial => 'hosts/sp_subnet', :locals => {:item => @host}))
page['#media_select'].html(render(:partial => 'common/os_selection/operatingsystem', :locals => {:item => @host})) if @operatingsystem
if @domain
page['*[id*=domain_id]'].val(@domain.id)
if @domain.subnets.any?
page['#subnet_select'].html(render(:partial => 'common/domain', :locals => {:item => @host}))
page['#host_subnet_id'].val(@subnet.id).change if @subnet
page['#sp_subnet'].html(render(:partial => 'hosts/sp_subnet', :locals => {:item => @host}))
end
end
end
end
end
end
def process_taxonomy
location = organization = nil
organization = Organization.find(params[:host][:organization_id]) unless params[:host][:organization_id].empty?
location = Location.find(params[:host][:location_id]) unless params[:host][:location_id].empty?
return head(:not_found) unless location || organization
@host = Host.new(params[:host])
Taxonomy.as_taxonomy organization, location do
render :partial => "form"
end
end
def template_used
kinds = params[:provisioning] == 'image' ? [TemplateKind.find_by_name('finish')] : TemplateKind.all
templates = kinds.map do |kind|
......
def load_vars_for_ajax
return unless @host
@environment = @host.environment
@architecture = @host.architecture
@domain = @host.domain
......
if @host.compute_resource_id && params[:host] && params[:host][:compute_attributes]
@host.compute_attributes = params[:host][:compute_attributes]
end
end
def find_multiple
app/controllers/locations_controller.rb
class LocationsController < ApplicationController
include Foreman::Controller::AutoCompleteSearch
before_filter :find_location, :only => %w{edit update destroy clone}
skip_before_filter :authorize, :set_taxonomy, :only => %w{select}
def index
begin
values = Location.my_locations.search_for(params[:search], :order => params[:order])
rescue => e
error e.to_s
values = Location.my_locations.search_for('')
end
respond_to do |format|
format.html do
@locations = values.paginate :page => params[:page]
end
end
end
def new
@location = Location.new
Taxonomy.no_taxonomy_scope do
# we explicitly render here in order to evaluate the view without taxonomy scope
render :new
end
end
def clone
new = @location.clone
# copy all the relations
new.name = ""
new.users = @location.users
new.smart_proxies = @location.smart_proxies
new.subnets = @location.subnets
new.compute_resources = @location.compute_resources
new.media = @location.media
new.domains = @location.domains
new.media = @location.media
new.hostgroups = @location.hostgroups
new.organizations = @location.organizations
@location = new
render :action => :new
end
def create
@location = Location.new(params[:location])
if @location.save
process_success
else
process_error
end
end
def edit
Taxonomy.no_taxonomy_scope do
# we explicitly render here in order to evaluate the view without taxonomy scope
render :edit
end
end
def update
result = Taxonomy.no_taxonomy_scope do
@location.update_attributes(params[:location])
end
if result
process_success
else
process_error
end
end
def destroy
if @location.destroy
process_success
else
process_error
end
end
def select
@location = params[:id] ? Location.find(params[:id]) : nil
Location.current = @location
session[:location_id] = @location ? @location.id : nil
redirect_back_or_to root_url
end
private
def find_location
@location = Location.find(params[:id])
end
end
app/controllers/organizations_controller.rb
class OrganizationsController < ApplicationController
include Foreman::Controller::AutoCompleteSearch
before_filter :find_organization, :only => %w{edit update destroy clone}
skip_before_filter :authorize, :set_taxonomy, :only => %w{select}
def index
begin
values = Organization.my_organizations.search_for(params[:search], :order => params[:order])
rescue => e
error e.to_s
values = Organization.my_organizations.search_for ""
end
respond_to do |format|
format.html do
@organizations = values.paginate :page => params[:page]
end
end
end
def new
@organization = Organization.new
Taxonomy.no_taxonomy_scope do
render :new
end
end
def clone
new = @organization.clone
# copy all the relations
new.name = ""
new.users = @organization.users
new.smart_proxies = @organization.smart_proxies
new.subnets = @organization.subnets
new.compute_resources = @organization.compute_resources
new.media = @organization.media
new.domains = @organization.domains
new.media = @organization.media
new.hostgroups = @organization.hostgroups
new.locations = @organization.locations
@organization = new
render :action => :new
end
def create
@organization = Organization.new(params[:organization])
if @organization.save
process_success
else
process_error
end
end
def edit
Taxonomy.no_taxonomy_scope do
render :edit
end
end
def update
result = Taxonomy.no_taxonomy_scope do
@organization.update_attributes(params[:organization])
end
if result
process_success
else
process_error
end
end
def destroy
if @organization.destroy
process_success
else
process_error
end
end
def select
@organization = params[:id] ? Organization.find(params[:id]) : nil
Organization.current = @organization
session[:org_id] = @organization ? @organization.id : nil
redirect_back_or_to root_url
end
private
def find_organization
@organization = Organization.find(params[:id])
end
end
app/controllers/reports_controller.rb
skip_before_filter :require_ssl, :only => :create
skip_before_filter :authorize, :only => :create
skip_before_filter :verify_authenticity_token, :only => :create
skip_before_filter :set_taxonomy, :only => :create
skip_before_filter :session_expiry, :update_activity_time, :only => :create
before_filter :set_admin_user, :only => :create
before_filter :setup_search_options, :only => :index
app/controllers/tasks_controller.rb
class TasksController < ApplicationController
skip_before_filter :session_expiry, :update_activity_time, :only => [:show]
skip_before_filter :session_expiry, :update_activity_time, :set_taxonomy, :only => [:show]
def show
id = params[:id]
app/controllers/topbar_sweeper.rb
class TopbarSweeper < ActionController::Caching::Sweeper
observe [Bookmark, User, UserRole] # This sweeper is going to keep an eye on the Bookmark model
observe [Bookmark, User, UserRole, Organization, Location] # This sweeper is going to keep an eye on the Bookmark model
def after_create(record)
expire_cache_for(record)
......
expire_cache_for(record)
end
def after_select(record)
expire_cache_for(record)
end
private
def expire_cache_for(record)
expire_fragment("tabs_and_title_records-#{User.current.id}") if User.current
app/controllers/unattended_controller.rb
FINISH_URLS = [:preseed_finish, :jumpstart_finish] + TemplateKind.where("name LIKE ?", "finish").map(&:name)
# We dont require any of these methods for provisioning
skip_before_filter :require_ssl, :require_login, :authorize, :session_expiry, :update_activity_time
skip_before_filter :require_ssl, :require_login, :authorize, :session_expiry, :update_activity_time, :set_taxonomy
# require logged in user to see templates in spoof mode
before_filter do |c|
app/controllers/users_controller.rb
include Foreman::Controller::AutoCompleteSearch
skip_before_filter :require_mail, :only => [:edit, :update, :logout]
skip_before_filter :require_login, :authorize, :session_expiry, :update_activity_time, :only => [:login, :logout]
skip_before_filter :require_login, :authorize, :session_expiry, :update_activity_time, :set_taxonomy, :only => [:login, :logout]
after_filter :update_activity_time, :only => :login
attr_accessor :editing_self
app/helpers/home_helper.rb
['Smart Proxies', :smart_proxies]
]
choices += [ [:divider] ]
if (SETTINGS[:organizations_enabled] or SETTINGS[:locations_enabled])
choices += [ ['Locations', :locations] ] if SETTINGS[:locations_enabled]
choices += [ ['Organizations', :organizations] ] if SETTINGS[:organizations_enabled]
choices += [ [:divider ] ]
end
if SETTINGS[:unattended]
choices += [
['Compute Resources', :compute_resources]
]
choices += [
[:divider],
['Architectures', :architectures],
['Compute Resources', :compute_resources],
['Domains', :domains],
['Hardware Models', :models],
['Installation Media', :media],
app/helpers/hosts_helper.rb
["Operating System", host.os],
["Host Group", host.hostgroup],
]
fields += [["Location", Location.find(host.location_id).name]] unless host.location_id.nil?
fields += [["Organization", Organization.find(host.organization_id).name]] unless host.organization_id.nil?
fields += [["Owner", host.owner]] if SETTINGS[:login]
fields += [["Certificate Name", host.certname]] if Setting[:use_uuid_for_certificates]
fields
app/helpers/organization_helper.rb
module OrganizationHelper
end
app/helpers/taxonomy_helper.rb
module TaxonomyHelper
def show_location_tab?
SETTINGS[:locations_enabled] && User.current.allowed_to?(:view_locations)
end
def show_organization_tab?
SETTINGS[:organizations_enabled] && User.current.allowed_to?(:view_organizations)
end
def show_taxonomy_tabs?
SETTINGS[:locations_enabled] or SETTINGS[:organizations_enabled]
end
def show_add_location_button? count
count ==0 && User.current.allowed_to?(:create_locations)
end
def show_add_organization_button? count
count == 0 && User.current.allowed_to?(:create_organizations)
end
def organization_dropdown count
text = Organization.current.nil? ? "Organizations" : Organization.current.to_label
if count == 1
link_to text, "#", :title => "Current Organization"
else
link_to((text + content_tag(:span,'', :class=>"caret")).html_safe, "#", :class => "dropdown-toggle", :'data-toggle'=>"dropdown", :title => "Current Organization")
end
end
def location_dropdown count
text = Location.current.nil? ? "Locations" : Location.current.to_label
if count == 1
link_to text, "#", :title => "Current Location"
else
link_to((text + content_tag(:span,'', :class=>"caret")).html_safe, "#", :class => "dropdown-toggle", :'data-toggle'=>"dropdown", :title => "Current Location")
end
end
end
app/models/compute_resource.rb
require 'fog_extensions'
class ComputeResource < ActiveRecord::Base
include Taxonomix
PROVIDERS = %w[ Libvirt Ovirt EC2 Vmware Openstack].delete_if{|p| p == "Libvirt" && !SETTINGS[:libvirt]}
audited :except => [:password, :attrs]
serialize :attrs, Hash
......
has_many :images, :dependent => :destroy
before_validation :set_attributes_hash
default_scope :order => 'LOWER(compute_resources.name)'
# with proc support, default_scope can no longer be chained
# include all default scoping here
default_scope lambda {
with_taxonomy_scope do
order("LOWER(compute_resources.name)")
end
}
scope :my_compute_resources, lambda {
user = User.current
app/models/domain.rb
# This models a DNS domain and so represents a site.
class Domain < ActiveRecord::Base
include Authorization
include Taxonomix
has_many :hosts
has_many :hostgroups
#order matters! see https://github.com/rails/rails/issues/670
......
belongs_to :dns, :class_name => "SmartProxy"
has_many :domain_parameters, :dependent => :destroy, :foreign_key => :reference_id
has_and_belongs_to_many :users, :join_table => "user_domains"
accepts_nested_attributes_for :domain_parameters, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
validates_uniqueness_of :name
validates_uniqueness_of :fullname, :allow_blank => true, :allow_nil => true
......
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
default_scope :order => 'LOWER(domains.name)'
# with proc support, default_scope can no longer be chained
# include all default scoping here
default_scope lambda {
with_taxonomy_scope do
order("LOWER(domains.name)")
end
}
class Jail < Safemode::Jail
allow :name, :fullname
app/models/environment.rb
class Environment < ActiveRecord::Base
include Taxonomix
has_many :environment_classes, :dependent => :destroy
has_many :puppetclasses, :through => :environment_classes, :uniq => true
has_many :hosts
......
has_many :template_combinations
before_destroy EnsureNotUsedBy.new(:hosts)
default_scope :order => 'environments.name'
# with proc support, default_scope can no longer be chained
# include all default scoping here
default_scope lambda {
with_taxonomy_scope do
order("LOWER(environments.name)")
end
}
scoped_search :on => :name, :complete_value => :true
app/models/host.rb
belongs_to :sp_subnet, :class_name => "Subnet"
belongs_to :compute_resource
belongs_to :image
belongs_to :location
belongs_to :organization
has_one :token, :dependent => :destroy, :conditions => Proc.new {"expires >= '#{Time.now.utc.to_s(:db)}'"}
has_many :lookup_values, :finder_sql => Proc.new { normalize_hostname; %Q{ SELECT lookup_values.* FROM lookup_values WHERE (lookup_values.match = 'fqdn=#{fqdn}') } }, :dependent => :destroy
......
include HostCommon
class Jail < ::Safemode::Jail
allow :name, :diskLayout, :puppetmaster, :puppet_ca_server, :operatingsystem, :os, :environment, :ptable, :hostgroup, :url_for_boot,
:params, :info, :hostgroup, :compute_resource, :domain, :ip, :mac, :shortname, :architecture, :model, :certname, :capabilities,
:provider, :subnet, :token
allow :name, :diskLayout, :puppetmaster, :puppet_ca_server, :operatingsystem, :os, :environment, :ptable, :hostgroup, :location,
:organization, :url_for_boot, :params, :info, :hostgroup, :compute_resource, :domain, :ip, :mac, :shortname, :architecture,
:model, :certname, :capabilities, :provider, :subnet, :token
end
attr_reader :cached_host_params
default_scope lambda {
org = Organization.current
loc = Location.current
conditions = {}
conditions[:organization_id] = org.id if org
conditions[:location_id] = loc.id if loc
where(conditions)
}
scope :recent, lambda { |*args| {:conditions => ["last_report > ?", (args.first || (Setting[:puppet_interval] + 5).minutes.ago)]} }
scope :out_of_sync, lambda { |*args| {:conditions => ["last_report < ? and enabled != ?", (args.first || (Setting[:puppet_interval] + 5).minutes.ago), false]} }
......
domain_conditions = sanitize_sql_for_conditions([" (hosts.domain_id in (?))",dms = (user.domains).map(&:id)])
compute_resource_conditions = sanitize_sql_for_conditions([" (hosts.compute_resource_id in (?))",(crs = user.compute_resources).map(&:id)])
hostgroup_conditions = sanitize_sql_for_conditions([" (hosts.hostgroup_id in (?))",(hgs = user.hostgroups).map(&:id)])
organization_conditions = sanitize_sql_for_conditions([" (hosts.organization_id in (?))",orgs = (user.organizations).map(&:id)])
location_conditions = sanitize_sql_for_conditions([" (hosts.location_id in (?))",locs = (user.locations).map(&:id)])
fact_conditions = ""
for user_fact in (ufs = user.user_facts)
......
(conditions = (user.compute_resources_andor == "and") ? "(#{conditions}) and #{compute_resource_conditions} " : "#{conditions} or #{compute_resource_conditions} ") unless crs.empty?
(conditions = (user.hostgroups_andor == "and") ? "(#{conditions}) and #{hostgroup_conditions} " : "#{conditions} or #{hostgroup_conditions} ") unless hgs.empty?
(conditions = (user.facts_andor == "and") ? "(#{conditions}) and #{fact_conditions} " : "#{conditions} or #{fact_conditions} ") unless ufs.empty?
(conditions = (user.organizations_andor == "and") ? "(#{conditions}) and #{organization_conditions} " : "#{conditions} or #{organization_conditions} ") unless orgs.empty?
(conditions = (user.locations_andor == "and") ? "(#{conditions}) and #{location_conditions} " : "#{conditions} or #{location_conditions} ") unless locs.empty?
conditions.sub!(/\s*\(\)\s*/, "")
conditions.sub!(/^(?:\(\))?\s?(?:and|or)\s*/, "")
conditions.sub!(/\(\s*(?:or|and)\s*\(/, "((")
app/models/hostext/search.rb
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
app/models/hostgroup.rb
class Hostgroup < ActiveRecord::Base
has_ancestry :orphan_strategy => :rootify
include Authorization
include Taxonomix
include HostCommon
has_many :hostgroup_classes, :dependent => :destroy
has_many :puppetclasses, :through => :hostgroup_classes
......
audited
has_many :trends, :as => :trendable, :class_name => "ForemanTrend"
# with proc support, default_scope can no longer be chained
# include all default scoping here
default_scope lambda { with_taxonomy_scope }
scoped_search :on => :name, :complete_value => :true
scoped_search :in => :group_parameters, :on => :value, :on_key=> :name, :complete_value => true, :only_explicit => true, :rename => :params
scoped_search :in => :hosts, :on => :name, :complete_value => :true, :rename => "host"
app/models/location.rb
class Location < Taxonomy
include Foreman::ThreadSession::LocationModel
has_and_belongs_to_many :organizations
has_many :hosts
before_destroy EnsureNotUsedBy.new(:hosts)
scope :completer_scope, lambda { my_locations }
scope :my_locations, lambda {
user = User.current
if user.admin?
conditions = { }
else
conditions = sanitize_sql_for_conditions([" (taxonomies.id in (?))", user.locations.map(&:id)])
end
where(conditions).reorder('type, name')
}
end
app/models/medium.rb
class Medium < ActiveRecord::Base
include Authorization
include Taxonomix
has_and_belongs_to_many :operatingsystems
has_many :hosts
......
:if => Proc.new { |m| m.respond_to? :media_path }
before_destroy EnsureNotUsedBy.new(:hosts)
default_scope :order => 'LOWER(media.name)'
# with proc support, default_scope can no longer be chained
# include all default scoping here
default_scope lambda {
with_taxonomy_scope do
order("LOWER(media.name)")
end
}
scoped_search :on => :name, :complete_value => :true, :default_order => true
scoped_search :on => :path, :complete_value => :true
scoped_search :on => :os_family, :rename => "family", :complete_value => :true
app/models/organization.rb
class Organization < Taxonomy
include Foreman::ThreadSession::OrganizationModel
has_and_belongs_to_many :locations
has_many :hosts
before_destroy EnsureNotUsedBy.new(:hosts)
scope :completer_scope, lambda { my_organizations }
scope :my_organizations, lambda {
user = User.current
if user.admin?
conditions = { }
else
conditions = sanitize_sql_for_conditions([" (taxonomies.id in (?))", user.organizations.map(&:id)])
end
where(conditions).reorder('type, name')
}
end
app/models/smart_proxy.rb
class SmartProxy < ActiveRecord::Base
include Taxonomix
ProxyFeatures = %w[ TFTP BMC DNS DHCP Puppetca Puppet]
attr_accessible :name, :url
#TODO check if there is a way to look into the tftp_id too
......
before_save :sanitize_url, :associate_features
before_destroy EnsureNotUsedBy.new(:subnets, :domains, :hosts, :hostgroups)
default_scope :order => 'LOWER(smart_proxies.name)'
# with proc support, default_scope can no longer be chained
# include all default scoping here
default_scope lambda {
with_taxonomy_scope do
order("LOWER(smart_proxies.name)")
end
}
ProxyFeatures.each {|f| scope "#{f.downcase}_proxies".to_sym, where(:features => {:name => f}).joins(:features) }
def hostname
app/models/subnet.rb
require 'ipaddr'
class Subnet < ActiveRecord::Base
include Authorization
include Taxonomix
has_many :hosts
# sps = Service processors / ilom boards etc
has_many :sps, :class_name => "Host", :foreign_key => 'sp_subnet_id'
......
validates_format_of :network, :mask, :with => Net::Validations::IP_REGEXP
validates_format_of :gateway, :dns_primary, :dns_secondary, :with => Net::Validations::IP_REGEXP, :allow_blank => true, :allow_nil => true
validate :name_should_be_uniq_across_domains
default_scope :order => 'priority'
validate :validate_ranges
default_scope lambda {
with_taxonomy_scope do
order('vlanid')
end
}
before_destroy EnsureNotUsedBy.new(:hosts, :sps)
scoped_search :on => [:name, :network, :mask, :gateway, :dns_primary, :dns_secondary, :vlanid], :complete_value => true
......
# [+other+] : Subnet object with which to compare ourself
# +returns+ : Subnet object with higher precedence
def <=> (other)
self.priority <=> other.priority
self.vlanid <=> other.vlanid
end
# Given an IP returns the subnet that contains that IP
app/models/taxable_taxonomy.rb
class TaxableTaxonomy < ActiveRecord::Base
belongs_to :taxonomy
belongs_to :taxable, :polymorphic => true
validates_uniqueness_of :taxonomy_id, :scope => [:taxable_id, :taxable_type]
end
app/models/taxonomix.rb
module Taxonomix
def self.included(base)
base.send :include, InstanceMethods
base.class_eval do
@taxonomy_join_table = "taxable_taxonomies"
@primary_key = "taxable_id"
has_many @taxonomy_join_table, :dependent => :destroy, :as => :taxable
has_many :locations, :through => @taxonomy_join_table, :source => :taxonomy, :class_name => 'Location'
has_many :organizations, :through => @taxonomy_join_table, :source => :taxonomy, :class_name => 'Organization'
after_initialize :set_current_taxonomy
scoped_search :in => :locations, :on => :name, :rename => :location, :complete_value => true
scoped_search :in => :organizations, :on => :name, :rename => :organization, :complete_value => true
def self.with_taxonomy_scope
scope = block_given? ? yield : where(1)
scope = scope.joins(taxonomy_join_condition 'loc1').where("loc1.taxonomy_id in (?)", Location.current.id) if SETTINGS[:locations_enabled] and Location.current
scope = scope.joins(taxonomy_join_condition 'org1').where("org1.taxonomy_id in (?)", Organization.current.id) if SETTINGS[:organizations_enabled] and Organization.current
scope
end
def self.taxonomy_join_condition name
" INNER JOIN #{@taxonomy_join_table} #{name} ON #{name}.#{@primary_key} = #{self.table_name}.id and #{name}.taxable_type = '#{self.to_s}'"
end
end
end
module InstanceMethods
def set_current_taxonomy
if self.new_record? && self.errors.empty?
self.locations << Location.current if Taxonomy.locations_enabled and Location.current
self.organizations << Organization.current if Taxonomy.organizations_enabled and Organization.current
end
end
end
end
app/models/taxonomy.rb
class Taxonomy < ActiveRecord::Base
audited
has_associated_audits
validates_presence_of :name
validates_uniqueness_of :name, :scope => :type
belongs_to :user
has_many :taxable_taxonomies, :dependent => :destroy
has_many :users, :through => :taxable_taxonomies, :source => :taxable, :source_type => 'User'
has_many :smart_proxies, :through => :taxable_taxonomies, :source => :taxable, :source_type => 'SmartProxy'
has_many :compute_resources, :through => :taxable_taxonomies, :source => :taxable, :source_type => 'ComputeResource'
has_many :media, :through => :taxable_taxonomies, :source => :taxable, :source_type => 'Medium'
has_many :domains, :through => :taxable_taxonomies, :source => :taxable, :source_type => 'Domain'
has_many :hostgroups, :through => :taxable_taxonomies, :source => :taxable, :source_type => 'Hostgroup'
has_many :environments, :through => :taxable_taxonomies, :source => :taxable, :source_type => 'Environment'
has_many :subnets, :through => :taxable_taxonomies, :source => :taxable, :source_type => 'Subnet'
scoped_search :on => :name, :complete_value => true
def to_param
"#{id.to_s.parameterize}"
end
def to_label
name =~ /[A-Z]/ ? name : name.capitalize
end
def self.locations_enabled
SETTINGS[:locations_enabled]
end
def self.organizations_enabled
SETTINGS[:organizations_enabled]
end
def self.no_taxonomy_scope
as_taxonomy nil, nil do
yield if block_given?
end
end
def self.as_taxonomy org, location
Organization.as_org org do
Location.as_location location do
yield if block_given?
end
end
end
end
app/models/user.rb
class User < ActiveRecord::Base
include Authorization
include Foreman::ThreadSession::UserModel
include Taxonomix
audited :except => [:last_login_on, :password, :password_hash, :password_salt, :password_confirmation]
self.auditing_enabled = !defined?(Rake)
......
has_many :user_facts, :dependent => :destroy
has_many :facts, :through => :user_facts, :source => :fact_name
scope :except_admin, where(:admin => false)
accepts_nested_attributes_for :user_facts, :reject_if => lambda { |a| a[:criteria].blank? }, :allow_destroy => true
validates :mail, :format => { :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)*[a-z]{2,})$/i },
......
scoped_search :on => :last_login_on, :complete_value => :true
scoped_search :in => :roles, :on => :name, :rename => :role, :complete_value => true
default_scope lambda {
with_taxonomy_scope do
order('firstname')
end
}
def to_label
"#{firstname} #{lastname}"
end
......
compute_resources.any? or
domains.any? or
hostgroups.any? or
facts.any?
facts.any? or
locations.any? or
organizations.any?
end
private
app/views/common/_edit_habtm.erb
<% if associations.empty? -%>
<strong>None Found</strong>
<% else -%>
<%= link_to_function(icon_text("check", ""), "toggleCheckboxesBySelector(\"[id$='#{ActiveModel::Naming.singular(associations.first)}_ids_']\")",
:title => "Select all") %>
<% associations.sort{|a,b| a.to_s <=> b.to_s}.each do |association| -%>
<li>
<%= content_tag_for :label, association do %>
<%= check_box_tag(
"#{prefix || klass.class.table_name.singularize}[#{ActiveModel::Naming.singular(association)}_ids][]",
association.id, klass.send(association.class.table_name).map(&:id).include?(association.id))
"#{prefix || klass.class.model_name.downcase}[#{ActiveModel::Naming.singular(association)}_ids][]",
association.id, klass.send(ActiveModel::Naming.plural(association)).map(&:id).include?(association.id))
%>
<%= contract association %>
<%= hidden_field_tag "#{prefix || klass.class.table_name.singularize}[#{ActiveModel::Naming.singular(association)}_ids][]" %>
<%= hidden_field_tag "#{prefix || klass.class.model_name.downcase}[#{ActiveModel::Naming.singular(association)}_ids][]" %>
<% end -%>
</li>
<% end -%>
app/views/compute_resources/_form.html.erb
<%= javascript "compute_resource" %>
<%= form_for(@compute_resource) do |f| %>
<%= form_for @compute_resource do |f| %>
<%= base_errors_for @compute_resource %>
<%= text_f f, :name %>
<%= selectable_f f, :provider, ComputeResource::PROVIDERS, { :include_blank => "Choose a provider"},
{:disabled=> f.object.uuid.present?, :'data-url'=> provider_selected_compute_resources_path, :onchange => 'providerSelected(this);'} %>
<%= textarea_f f, :description, :rows => 3 %>
<div id='compute_connection'>
<%= render "compute_resources/form/#{@compute_resource.provider.downcase}", :f => f unless @compute_resource.provider.empty? %>
</div>
<% if show_taxonomy_tabs? %>
<ul class="nav nav-tabs" data-tabs="tabs">
<li class="active"><a href="#primary" data-toggle="tab">Compute Resource</a></li>
<% if show_location_tab? %>
<li><a href="#locations" data-toggle="tab">Locations</a></li>
<% end %>
<% if show_organization_tab? %>
<li><a href="#organizations" data-toggle="tab">Organizations</a></li>
<% end %>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="primary">
<% end %>
<%= text_f f, :name %>
<%= selectable_f f, :provider, ComputeResource::PROVIDERS, { :include_blank => "Choose a provider"},
{:disabled=> f.object.uuid.present?, :'data-url'=> provider_selected_compute_resources_path, :onchange => 'providerSelected(this);'} %>
<%= textarea_f f, :description, :rows => 3 %>
<div id='compute_connection'>
<%= render "compute_resources/form/#{@compute_resource.provider.downcase}", :f => f unless @compute_resource.provider.empty? %>
</div>
<% if show_taxonomy_tabs? %>
</div>
<% if show_location_tab? %>
<div class="tab-pane" id="locations">
<%= multiple_checkboxes f, :locations, @compute_resource, Location %>
</div>
<% end %>
<% if show_organization_tab? %>
<div class="tab-pane" id="organizations">
<%= multiple_checkboxes f, :organizations, @compute_resource, Organization %>
</div>
<% end %>
</div>
<% end %>
<%= submit_or_cancel f %>
<% end %>
app/views/domains/_form.html.erb
<ul class="nav nav-tabs" data-tabs="tabs">
<li class="active"><a href="#primary" data-toggle="tab">Domain</a></li>
<li><a href="#params" data-toggle="tab">Parameters</a></li>
<% if show_location_tab? %>
<li><a href="#locations" data-toggle="tab">Locations</a></li>
<% end %>
<% if show_organization_tab? %>
<li><a href="#organizations" data-toggle="tab">Organizations</a></li>
<% end %>
</ul>
<div class="tab-content">
......
<%= render "common_parameters/parameters", { :f => f, :type => :domain_parameters } %>
</div>
<% if show_location_tab? %>
<div class="tab-pane" id="locations">
<%= multiple_checkboxes f, :locations, @domain, Location %>
</div>
<% end %>
<% if show_organization_tab? %>
<div class="tab-pane" id="organizations">
<%= multiple_checkboxes f, :organizations, @domain, Organization %>
</div>
<% end %>
</div>
<%= submit_or_cancel f %>
app/views/environments/_form.html.erb
<%= form_for @environment do |f| %>
<%= base_errors_for @environment %>
<%= text_f f, :name %>
<% if show_taxonomy_tabs? %>
<ul class="nav nav-tabs" data-tabs="tabs">
<li class="active"><a href="#primary" data-toggle="tab">Primary</a></li>
<% if show_location_tab? %>
<li><a href="#locations" data-toggle="tab">Locations</a></li>
<% end %>
<% if show_organization_tab? %>
<li><a href="#organizations" data-toggle="tab">Organizations</a></li>
<% end %>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="primary">
<%= text_f f, :name %>
</div>
<% if show_location_tab? %>
<div class="tab-pane" id="locations">
<%= multiple_checkboxes f, :locations, @environment, Location, { :prefix => "environment" } %>
</div>
<% end %>
<% if show_organization_tab? %>
<div class="tab-pane" id="organizations">
<%= multiple_checkboxes f, :organizations, @environment, Organization, { :prefix => "environment" } %>
</div>
<% end %>
</div>
<% else %>
<%= text_f f, :name %>
<% end %>
<%= submit_or_cancel f %>
<% end %>
app/views/home/_location_dropdown.rhtml
<li class="dropdown">
<% location_count = Location.my_locations.count %>
<%= location_dropdown location_count %>
<ul class="dropdown-menu">
<% if show_add_location_button? location_count%>
<div class="no-taxonomies">
<div class="alert">
<button type="button" class="close" data-dismiss="alert">&times;</button>
There aren't any locations yet.
</div>
<button class="btn btn-primary" type="button"><%= link_to "New Location", :controller => "locations", :action => "new" %></button>
</div>
<% elsif location_count > 1 %>
<li><%= link_to('Any Location', clear_locations_path) %></li>
<% Location.my_locations.each do |location| %>
<li><%= link_to(location.name, select_location_path(location)) %></li>
<% end -%>
<% end %>
</ul>
</li>
app/views/home/_organization_dropdown.rhtml
<li class="dropdown">
<% orgs_count = Organization.my_organizations.count %>
<%= organization_dropdown orgs_count %>
<ul class="dropdown-menu">
<% if show_add_organization_button? orgs_count %>
<div class="no-taxonomies">
<div class="alert">
<button type="button" class="close" data-dismiss="alert">&times;</button>
There aren't any organizations yet.
</div>
<button class="btn btn-primary" type="button"><%= link_to "New Organization", :controller => "organizations", :action => "new" %></button>
</div>
<% elsif orgs_count > 1 %>
<li><%= link_to('All', clear_organizations_path) %></li>
<% Organization.my_organizations.each do |organization| %>
<li><%= link_to(organization.name, select_organization_path(organization)) %></li>
<% end %>
<% end %>
</ul>
</li>
app/views/home/_topbar.rhtml
<ul class="nav" id="menu">
<% my_bookmarks = Bookmark.my_bookmarks %>
<%= menu 'dashboard', my_bookmarks %>
<%= menu 'hosts', my_bookmarks %>
<%= menu 'reports', my_bookmarks, hash_for_reports_path.merge(:search => 'eventful = true') %>
<%= menu 'facts' , my_bookmarks, hash_for_fact_values_path %>
<%= menu 'audits', my_bookmarks, hash_for_audits_path %>
<%= menu 'statistics', my_bookmarks %>
<%= menu 'trends', my_bookmarks, hash_for_trends_path %>
</ul>
</div>
<div class="nav-collapse nav2">
<ul class="nav pull-right" id="menu2">
<%= render "home/settings" -%>
<% if SETTINGS[:login]%>
<%= render 'home/user_dropdown' %>
<%= menu 'dashboard', my_bookmarks %>
<%= menu 'hosts', my_bookmarks %>
<%= menu 'reports', my_bookmarks, hash_for_reports_path.merge(:search => 'eventful = true') %>
<%= menu 'facts' , my_bookmarks, hash_for_fact_values_path %>
<%= menu 'audits', my_bookmarks, hash_for_audits_path %>
<%= menu 'statistics', my_bookmarks %>
<%= menu 'trends', my_bookmarks, hash_for_trends_path %>
</ul>
</div>
<div class="nav-collapse nav2">
<ul class="nav pull-right" id="menu2">
<% if SETTINGS[:login] %>
<% if SETTINGS[:locations_enabled] %>
<%= render 'home/location_dropdown' %>
<% end -%>
</ul>
<% if SETTINGS[:organizations_enabled] %>
<%= render 'home/organization_dropdown' %>
<% end %>
<% end %>
<%= render "home/settings" -%>
<% if SETTINGS[:login]%>
<%= render 'home/user_dropdown' %>
<% end -%>
</ul>
</div>
<% end -%>
<% end -%>
app/views/hostgroups/_form.html.erb
<li><a href="#os" data-toggle="tab">Operating System</a></li>
<% end -%>
<li><a href="#params" data-toggle="tab">Parameters</a></li>
<% if show_location_tab? %>
<li><a href="#locations" data-toggle="tab">Locations</a></li>
<% end %>
<% if show_organization_tab? %>
<li><a href="#organizations" data-toggle="tab">Organizations</a></li>
<% end %>
</ul>
<div class="tab-content">
......
<div class="tab-pane" id="params">
<%= render "common_parameters/parameters", { :f => f, :type => :group_parameters } %>
</div>
<% if show_location_tab? %>
<div class="tab-pane" id="locations">
<%= multiple_checkboxes f, :locations, @hostgroup, Location %>
</div>
<% end %>
<% if show_organization_tab? %>
<div class="tab-pane" id="organizations">
<%= multiple_checkboxes f, :organizations, @hostgroup, Organization %>
</div>
<% end %>
</div>
<%= submit_or_cancel f %>
app/views/hosts/_form.html.erb
<div class="tab-pane active" id="primary">
<%= text_f f, :name, :class => "input-xlarge", :value => name_field(@host) %>
<% if show_organization_tab? %>
<%= select_f f, :organization_id, Organization.my_organizations, :id, :to_label,
{ :include_blank => true },
{ :onchange => 'organization_changed(this);', :label => "Organization", :'data-host-id' => @host.id,
:'data-url' => process_taxonomy_hosts_path, :selected => Organization.my_organizations,
:help_inline => image_tag('spinner.gif', :id => 'organization_indicator', :class => 'hide').html_safe } %>
<% end %>
<% if show_location_tab? %>
<%= select_f f, :location_id, Location.my_locations, :id, :to_label,
{ :include_blank => true},
{ :onchange => 'location_changed(this);', :label => "Location", :"data-host-id" => @host.id,
:'data-url' => process_taxonomy_hosts_path, :selected => Location.my_locations,
:help_inline => image_tag('spinner.gif', :id => 'location_indicator', :class => 'hide').html_safe } %>
<% end %>
<%= select_f f, :compute_resource_id, ComputeResource.my_compute_resources, :id, :to_label,
{ :include_blank => 'Bare Metal' },
{:label => "Deploy on", :disabled => !@host.new_record?, :'data-url' => compute_resource_selected_hosts_path ,
:onchange => 'computeResourceSelected(this);'} if SETTINGS[:unattended] && @host.new_record? || @host.compute_resource_id %>
<%= select_f f, :hostgroup_id, accessible_hostgroups, :id, :to_label,
{ :include_blank => true},
{ :onchange => 'hostgroup_changed(this);' , :'data-host-id' => @host.id,
......
</div>
<%= textarea_f f, :comment, :help_block => "Additional information about this host", :class => "input-xxlarge", :rows => "3" %>
</div>
</div>
<%= f.hidden_field :overwrite? %>
<%= submit_or_cancel f, @host.overwrite? %>
<% end %>
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff