module Katello class KTEnvironment < Katello::Model audited :associations => [:content_facets] include ForemanTasks::Concerns::ActionSubject include Authorization::LifecycleEnvironment self.table_name = "katello_environments" include Ext::LabelFromName belongs_to :organization, :class_name => "Organization", :inverse_of => :kt_environments has_many :env_priors, :class_name => "Katello::EnvironmentPrior", :foreign_key => :environment_id, :dependent => :destroy has_many :priors, :class_name => "Katello::KTEnvironment", :through => :env_priors, :source => :env_prior has_many :env_successors, :class_name => "Katello::EnvironmentPrior", :foreign_key => :prior_id, :dependent => :destroy has_many :successors, :class_name => "Katello::KTEnvironment", :through => :env_successors, :source => :env has_many :repositories, :class_name => "Katello::Repository", dependent: :destroy, foreign_key: :environment_id has_many :content_view_histories, :class_name => "Katello::ContentViewHistory", :dependent => :destroy, :inverse_of => :environment, :foreign_key => :katello_environment_id has_many :capsule_lifecycle_environments, :foreign_key => :lifecycle_environment_id, :dependent => :destroy, :inverse_of => :lifecycle_environment has_many :capsules, :through => :capsule_lifecycle_environments, :foreign_key => :capsule_id, :class_name => "::SmartProxy", :inverse_of => :lifecycle_environments has_many :content_view_environments, :class_name => "Katello::ContentViewEnvironment", :foreign_key => :environment_id, :inverse_of => :environment, :dependent => :destroy has_many :content_view_environment_content_facets, :through => :content_view_environments, :class_name => "Katello::ContentViewEnvironmentContentFacet" has_many :content_facets, :through => :content_view_environment_content_facets, :class_name => "Katello::Host::ContentFacet" has_many :content_views, :through => :content_view_environments has_many :content_view_versions, :through => :content_view_environments, :inverse_of => :environments has_many :content_view_environment_activation_keys, :through => :content_view_environments, :class_name => "Katello::ContentViewEnvironmentActivationKey", :dependent => :restrict_with_exception has_many :activation_keys, :through => :content_view_environment_activation_keys, :class_name => "Katello::ActivationKey" has_many :hosts, :class_name => "::Host::Managed", :through => :content_facets, :inverse_of => :lifecycle_environments has_many :hostgroup_content_facets, :class_name => "Katello::Hostgroup::ContentFacet", :foreign_key => :lifecycle_environment_id, :inverse_of => :lifecycle_environment, :dependent => :restrict_with_exception has_many :hostgroups, :class_name => "::Hostgroup", :through => :hostgroup_content_facets, :inverse_of => :lifecycle_environment alias_method :content_sources, :capsules scope :completer_scope, ->(options = nil) { where('organization_id = ?', options[:organization_id]) if options[:organization_id].present? } scope :non_library, -> { where(library: false) } scope :library, -> { where(library: true) } validates_lengths_from_database :except => [:label] validates :organization, :presence => true validates :name, :presence => true, :uniqueness => {:scope => :organization_id, :message => N_("of environment must be unique within one organization")}, :exclusion => { :in => ["Library"], :message => N_(": '%s' is a built-in environment") % "Library", :unless => :library? } validates :label, :presence => true, :uniqueness => {:scope => :organization_id, :message => N_("of environment must be unique within one organization")}, :exclusion => { :in => ["Library"], :message => N_(": '%s' is a built-in environment") % "Library", :unless => :library? } validates :label, :exclusion => { :in => [ContentView::CONTENT_DIR], :message => N_(": '%s' is a built-in environment") % ContentView::CONTENT_DIR } validates_with Validators::KatelloNameFormatValidator, :attributes => :name validates_with Validators::KatelloLabelFormatValidator, :attributes => :label validates_with Validators::PriorValidator validates_with Validators::PathDescendentsValidator validates_with Katello::Validators::EnvironmentDockerRepositoriesValidator # RAILS3458: before_destroys before associations. see http://tinyurl.com/rails3458 before_destroy :assert_deletable, :prepend => true before_destroy :remove_from_path, :prepend => true scope(:not_in_capsule, lambda do |capsule| select("DISTINCT #{KTEnvironment.table_name}.*"). joins(%{LEFT OUTER JOIN #{CapsuleLifecycleEnvironment.table_name} ON ( "#{CapsuleLifecycleEnvironment.table_name}"."lifecycle_environment_id" = "#{KTEnvironment.table_name}"."id")}). where(%("#{CapsuleLifecycleEnvironment.table_name}"."capsule_id" IS NULL OR "#{CapsuleLifecycleEnvironment.table_name}"."capsule_id" != ?), capsule.id) end) after_create :add_to_default_capsule ERROR_CLASS_NAME = "Environment".freeze scoped_search :on => :name, :complete_value => true scoped_search :on => :label, :complete_value => true scoped_search :on => :organization_id, :complete_value => true, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER scoped_search :on => :id, :complete_value => true, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER def library? self.library end def default_content_view self.default_content_view_version.try(:content_view) end def default_content_view_version return nil unless self.organization.default_content_view self.organization.default_content_view.version(self) end def content_view_environment return nil unless self.default_content_view self.default_content_view.content_view_environments.where(:environment_id => self.id).first end def successor return self.successors[0] unless self.library? self.organization.promotion_paths[0][0] unless self.organization.promotion_paths.empty? end # creates new env from create_params with self as a prior def insert_successor(create_params, path) self.class.transaction do new_successor = self.class.create!(create_params) if library? if path old_successor = path.first old_successor.prior = new_successor end save_successor new_successor elsif successor.nil? save_successor new_successor else old_successor = successor old_successor.prior = new_successor save_successor new_successor end fail HttpErrors::UnprocessableEntity, _('An environment is missing a prior') unless all_have_prior? new_successor end end def content_source_associated?(capsule) return false if capsule.blank? capsule.pulp_primary? || self.content_sources.ids.include?(capsule.id) end def display_name self.name end def to_s display_name end # for multiselect helper in foreman def to_label return "#{name} (#{organization.title})" if organization && ::Organization.current.nil? name end def prior self.priors[0] end def prior=(env) env_id = env.is_a?(ActiveRecord::Base) ? env.id : env self.priors.clear return if env_id.nil? || env_id == "" prior_env = KTEnvironment.find env_id self.priors << prior_env unless prior_env.nil? end def path s = self.successor ret = [self] until s.nil? fail "Environment path has duplicates!!. #{self}. Duplicate => #{ret}. Path => #{s}" if ret.include? s ret << s s = s.successor end ret end #is the environment currently being promoted to def promoting_to? self.promoting.exists? end def assert_deletable throw :abort unless deletable? end def deletable? return true if self.organization.nil? || self.organization.being_deleted? if library? errors.add :base, _("Library lifecycle environments may not be deleted.") end if content_facets.any? errors.add(:base, _("Lifecycle Environment %s has associated Hosts." \ " Please unregister or move the associated Hosts before trying to delete this lifecycle environment.") % self.name) end if activation_keys.any? errors.add(:base, _("Lifecycle Environment %s has associated Activation Keys." \ " Please change or remove the associated Activation Keys before trying to delete this lifecycle environment.") % self.name) end return errors.empty? end #Unlike path which only gives the path from this environment going forward # Get the full path, that is go to the HEAD of the path this environment is on # and then give me that entire path def full_path p = self until p.prior.nil? || p.prior.library p = p.prior end p.prior.nil? ? p.path : [p.prior] + p.path end def available_products if self.prior.library # if there is no prior, then the prior is the Library, which has all products prior_products = self.organization.library.products else prior_products = self.prior.products end return prior_products - self.products end def products self.library? ? Product.in_org(self.organization) : Product.where(id: repositories.map(&:product_id)) end def as_json(_options = {}) to_ret = self.attributes to_ret['prior'] = self.prior && self.prior.name to_ret['prior_id'] = self.prior && self.prior.id to_ret['organization'] = self.organization && self.organization.name to_ret end def remove_from_path if self.successor && self.prior prior_env = self.prior self.env_priors.destroy_all self.successor.env_priors.first.update!(:prior_id => prior_env.id) end end def key_for(item) "environment_#{id}_#{item}" end def add_to_default_capsule SmartProxy.pulp_primary.try(:add_lifecycle_environment, self) end # Katello, which understands repository content and promotion, provides release versions based upon # enabled repos. Headpin, which does not traverse products to the repo level, exposes all release # versions in the manifest. def available_releases self.repositories.map(&:minor).compact.uniq.sort end def self.humanize_class_name _("Lifecycle Environment") end def self.permission_name 'lifecycle_environments' end apipie :class, desc: "A class representing #{model_name.human} object" do name 'Katello Environment' refs 'KTEnvironment' sections only: %w[all additional] prop_group :katello_basic_props, Katello::Model, meta: { friendly_name: 'Katello Environment' } end class Jail < ::Safemode::Jail allow :id, :name, :label end private def all_have_prior? organization.kt_environments.reject { |env| env.library? || env.prior.present? }.empty? end def save_successor(new_successor) new_successor.prior = self new_successor.save end end end