From 733509ef2a1258c34bd11dbdd5d950b727b07dd4 Mon Sep 17 00:00:00 2001 From: Partha Aji Date: Tue, 30 Jul 2024 15:54:51 -0400 Subject: [PATCH] Fixes #37795 - set multiple Content Views via a single Activation key --- .../api/v2/activation_keys_controller.rb | 128 +++++++++-------- .../api/v2/hosts_controller_extensions.rb | 21 +-- .../api/v2/content_views_controller.rb | 2 +- .../katello/activation_key/reassign.rb | 7 +- .../actions/katello/content_view/remove.rb | 20 ++- app/lib/katello/util/cveak_migrator.rb | 37 +++++ app/models/katello/activation_key.rb | 129 +++++++++++++----- .../katello/authorization/activation_key.rb | 2 +- .../authorization/content_view_environment.rb | 7 + app/models/katello/content_view.rb | 9 +- .../katello/content_view_environment.rb | 35 ++++- ...content_view_environment_activation_key.rb | 20 +++ app/models/katello/kt_environment.rb | 17 ++- .../katello/product_content_finder.rb | 9 +- app/services/katello/registration_manager.rb | 16 ++- .../api/v2/activation_keys/base.json.rabl | 23 +++- .../v2/content_view_versions/base.json.rabl | 2 +- .../api/v2/content_views/base.json.rabl | 2 +- ...content_view_environment_activation_key.rb | 85 ++++++++++++ ...content_view_environment_activation_key.rb | 5 + spec/models/activation_key_spec.rb | 23 ++-- .../api/v2/activation_keys_controller_test.rb | 2 +- .../api/v2/content_views_controller_test.rb | 8 +- .../models/katello_activation_keys.yml | 15 +- ...ntent_view_environment_activation_keys.yml | 26 ++++ test/models/activation_key_test.rb | 22 ++- .../activation_key_authorization_test.rb | 2 +- ...nt_view_environment_activation_key_test.rb | 29 ++++ test/services/product_content_finder_test.rb | 8 +- 29 files changed, 514 insertions(+), 197 deletions(-) create mode 100644 app/lib/katello/util/cveak_migrator.rb create mode 100644 app/models/katello/content_view_environment_activation_key.rb create mode 100644 db/migrate/20240730163043_add_content_view_environment_activation_key.rb create mode 100644 db/migrate/20240903194428_add_priority_to_content_view_environment_activation_key.rb create mode 100644 test/fixtures/models/katello_content_view_environment_activation_keys.yml create mode 100644 test/models/content_view_environment_activation_key_test.rb diff --git a/app/controllers/katello/api/v2/activation_keys_controller.rb b/app/controllers/katello/api/v2/activation_keys_controller.rb index 86b8b5e7f1d..7336da6cfb4 100644 --- a/app/controllers/katello/api/v2/activation_keys_controller.rb +++ b/app/controllers/katello/api/v2/activation_keys_controller.rb @@ -1,19 +1,43 @@ module Katello - class Api::V2::ActivationKeysController < Api::V2::ApiController + class Api::V2::ActivationKeysController < Api::V2::ApiController # rubocop:disable Metrics/ClassLength include Katello::Concerns::FilteredAutoCompleteSearch include Katello::Concerns::Api::V2::ContentOverridesController before_action :verify_presence_of_organization_or_environment, :only => [:index] - before_action :find_environment, :only => [:index, :create, :update] before_action :find_optional_organization, :only => [:index, :create, :show] - before_action :find_content_view, :only => [:index] before_action :find_authorized_katello_resource, :only => [:show, :update, :destroy, :available_releases, :available_host_collections, :add_host_collections, :remove_host_collections, :content_override, :add_subscriptions, :remove_subscriptions, :subscriptions] + before_action :find_content_view_environments, :only => [:create, :update] before_action :verify_simple_content_access_disabled, :only => [:add_subscriptions] before_action :validate_release_version, :only => [:create, :update] - wrap_parameters :include => (ActivationKey.attribute_names + %w(host_collection_ids service_level auto_attach purpose_role purpose_usage purpose_addons content_view_environment)) + wrap_parameters :include => (ActivationKey.attribute_names + %w(host_collection_ids service_level auto_attach purpose_role purpose_usage purpose_addons content_view_environments)) + + def_param_group :activation_key do + param :organization_id, :number, :desc => N_("organization identifier"), :required => true + param :name, String, :desc => N_("name"), :required => true + param :description, String, :desc => N_("description") + param :max_hosts, :number, :desc => N_("maximum number of registered content hosts") + param :unlimited_hosts, :bool, :desc => N_("can the activation key have unlimited hosts") + param :release_version, String, :desc => N_("content release version") + param :service_level, String, :desc => N_("service level") + param :auto_attach, :bool, :desc => N_("auto attach subscriptions upon registration"), deprecated: true + param :purpose_usage, String, :desc => N_("Sets the system purpose usage") + param :purpose_role, String, :desc => N_("Sets the system purpose usage") + param :purpose_addons, Array, :desc => N_("Sets the system add-ons") + + param :environment, Hash, :desc => N_("Hash containing the Id of the single lifecycle environment to be associated with the activation key."), deprecated: true + param :content_view_id, Integer, :desc => N_("Id of the single content view to be associated with the activation key.") + param :environment_id, Integer, :desc => N_("Id of the single lifecycle environment to be associated with the activation key.") + param :content_view_environments, Array, :desc => N_("Comma-separated list of Candlepin environment names to be associated with the activation key,"\ + " in the format of 'lifecycle_environment_label/content_view_label'."\ + " Ignored if content_view_environment_ids is specified, or if content_view_id and lifecycle_environment_id are specified."\ + " Requires allow_multiple_content_views setting to be on.") + param :content_view_environment_ids, Array, :desc => N_("Array of content view environment ids to be associated with the activation key."\ + " Ignored if content_view_id and lifecycle_environment_id are specified."\ + " Requires allow_multiple_content_views setting to be on.") + end api :GET, "/activation_keys", N_("List activation keys") api :GET, "/environments/:environment_id/activation_keys" @@ -22,31 +46,26 @@ class Api::V2::ActivationKeysController < Api::V2::ApiController param :environment_id, :number, :desc => N_("environment identifier") param :content_view_id, :number, :desc => N_("content view identifier") param :name, String, :desc => N_("activation key name to filter by") + param :content_view_environments, Array, :desc => N_("Comma-separated list of Candlepin environment names associated with the activation key,"\ + " in the format of 'lifecycle_environment_label/content_view_label'."\ + " Ignored if content_view_environment_ids is specified, or if content_view_id and lifecycle_environment_id are specified."\ + " Requires allow_multiple_content_views setting to be on.") + param :content_view_environment_ids, Array, :desc => N_("Array of content view environment ids associated with the activation key. " \ + "Ignored if content_view_id and lifecycle_environment_id are specified."\ + "Requires allow_multiple_content_views setting to be on.") + param_group :search, Api::V2::ApiController add_scoped_search_description_for(ActivationKey) def index - activation_key_includes = [:content_view, :environment, :host_collections, :organization] + activation_key_includes = [:content_view_environments, :host_collections, :organization] respond(:collection => scoped_search(index_relation.distinct, :name, :asc, :includes => activation_key_includes)) end api :POST, "/activation_keys", N_("Create an activation key") - param :organization_id, :number, :desc => N_("organization identifier"), :required => true - param :name, String, :desc => N_("name"), :required => true - param :description, String, :desc => N_("description") - param :environment, Hash, :desc => N_("environment") - param :environment_id, :number, :desc => N_("environment id") - param :content_view_id, :number, :desc => N_("content view id") - param :max_hosts, :number, :desc => N_("maximum number of registered content hosts") - param :unlimited_hosts, :bool, :desc => N_("can the activation key have unlimited hosts") - param :release_version, String, :desc => N_("content release version") - param :service_level, String, :desc => N_("service level") - param :auto_attach, :bool, :desc => N_("auto attach subscriptions upon registration"), deprecated: true - param :purpose_usage, String, :desc => N_("Sets the system purpose usage") - param :purpose_role, String, :desc => N_("Sets the system purpose usage") - param :purpose_addons, Array, :desc => N_("Sets the system add-ons") + param_group :activation_key def create @activation_key = ActivationKey.new(activation_key_params) do |activation_key| - activation_key.environment = @environment if @environment + activation_key.content_view_environments = @content_view_environments if @content_view_environments activation_key.organization = @organization activation_key.user = current_user end @@ -57,21 +76,10 @@ def create end api :PUT, "/activation_keys/:id", N_("Update an activation key") + param_group :activation_key param :id, :number, :desc => N_("ID of the activation key"), :required => true - param :organization_id, :number, :desc => N_("organization identifier"), :required => true - param :name, String, :desc => N_("name"), :required => false - param :description, String, :desc => N_("description") - param :environment_id, :number, :desc => N_("environment id") - param :content_view_id, :number, :desc => N_("content view id") - param :max_hosts, :number, :desc => N_("maximum number of registered content hosts") - param :unlimited_hosts, :bool, :desc => N_("can the activation key have unlimited hosts") - param :release_version, String, :desc => N_("content release version") - param :service_level, String, :desc => N_("service level") - param :auto_attach, :bool, :desc => N_("auto attach subscriptions upon registration") - param :purpose_usage, String, :desc => N_("Sets the system purpose usage") - param :purpose_role, String, :desc => N_("Sets the system purpose usage") - param :purpose_addons, Array, :desc => N_("Sets the system add-ons") def update + @activation_key.update!(content_view_environments: @content_view_environments) if @content_view_environments.present? sync_task(::Actions::Katello::ActivationKey::Update, @activation_key, activation_key_params) respond_for_show(:resource => @activation_key) end @@ -247,8 +255,9 @@ def index_relation activation_keys = ActivationKey.readable activation_keys = activation_keys.where(:name => params[:name]) if params[:name] activation_keys = activation_keys.where(:organization_id => @organization) if @organization - activation_keys = activation_keys.where(:environment_id => @environment) if @environment - activation_keys = activation_keys.where(:content_view_id => @content_view) if @content_view + activation_keys = activation_keys.in_content_views_and_environments(content_view_environments: @content_view_environments) if @content_view_environments + activation_keys = activation_keys.in_content_views_and_environments(content_views: params[:content_view_id]) if params[:content_view_id] + activation_keys = activation_keys.in_content_views_and_environments(lifecycle_environments: params[:lifecycle_environments]) if params[:lifecycle_environments] activation_keys end @@ -266,15 +275,33 @@ def subscription_index subscriptions end - def find_environment + def find_cve_for_single environment_id = params[:environment_id] - environment_id = params[:environment][:id] if !environment_id && params[:environment] - return unless environment_id + environment_id ||= params.dig(:environment, :id) + content_view_id = params[:content_view_id] + if environment_id.blank? || content_view_id.blank? + fail HttpErrors::BadRequest, _("Both environment ID and content view ID needs to be provided together") + end + cve = ::Katello::ContentViewEnvironment.readable.where(environment_id: environment_id, + content_view_id: content_view_id).first + if cve.blank? + fail HttpErrors::NotFound, _("Couldn't find content view environment with content view ID '%{cv}'"\ + " or environment ID '%{env}'") % { cv: content_view_id, env: environment_id } + end + @content_view_environments = [cve] + end - @environment = KTEnvironment.readable.find_by(id: environment_id) - fail HttpErrors::NotFound, _("Couldn't find environment '%s'") % params[:environment_id] if @environment.nil? - @organization = @environment.organization - @environment + def find_content_view_environments + @content_view_environments = [] + if params[:environment_id] || params[:environment] + find_cve_for_single + elsif params[:content_view_environments] || params[:content_view_environment_ids] + @content_view_environments = ::Katello::ContentViewEnvironment.fetch_content_view_environments( + labels: params[:content_view_environments], + ids: params[:content_view_environment_ids], + organization: @organization || @activation_key&.organization) + end + @organization ||= @content_view_environments.first&.organization end def find_host_collections @@ -293,14 +320,6 @@ def verify_presence_of_organization_or_environment fail HttpErrors::BadRequest, _("Either organization ID or environment ID needs to be specified") end - def find_content_view - if params.include?(:content_view_id) - cv_id = params[:content_view_id] - @content_view = ContentView.readable.find_by(:id => cv_id) - fail HttpErrors::NotFound, _("Couldn't find content view '%s'") % cv_id if @content_view.nil? - end - end - def permitted_params params.require(:activation_key).permit(:name, :description, @@ -316,14 +335,15 @@ def permitted_params :purpose_usage, :purpose_addon_ids, :content_overrides => [], - :host_collection_ids => []).to_h + :host_collection_ids => [], + :content_view_environments => [], + :content_view_environment_ids => []).to_h end def activation_key_params - key_params = permitted_params + key_params = permitted_params.except(:environment_id, :content_view_id, + :content_view_environments, :content_view_environment_ids) - key_params[:environment_id] = params[:environment][:id] if params[:environment].try(:[], :id) - key_params[:content_view_id] = params[:content_view][:id] if params[:content_view].try(:[], :id) unless params[:purpose_addons].nil? key_params[:purpose_addon_ids] = params[:purpose_addons].map { |addon| ::Katello::PurposeAddon.find_or_create_by(name: addon).id } end diff --git a/app/controllers/katello/concerns/api/v2/hosts_controller_extensions.rb b/app/controllers/katello/concerns/api/v2/hosts_controller_extensions.rb index 50a699fb58b..86fd203b61e 100644 --- a/app/controllers/katello/concerns/api/v2/hosts_controller_extensions.rb +++ b/app/controllers/katello/concerns/api/v2/hosts_controller_extensions.rb @@ -45,23 +45,12 @@ def set_content_view_environments content_facet_attributes = params.dig(:host, :content_facet_attributes) return if content_facet_attributes.blank? || @host&.content_facet.blank? || (cve_params[:content_view_id].present? && cve_params[:lifecycle_environment_id].present?) - new_cves = nil - if cve_params[:content_view_environments].present? && cve_params[:content_view_environment_ids].blank? - # Must do maps here to ensure CVEs remain in the same order. - # Using ActiveRecord .where will return them in a different order. - environment_names = cve_params[:content_view_environments].map(&:strip) - Rails.logger.debug "new environment names: #{environment_names}" - new_cves = environment_names.map do |name| - ::Katello::ContentViewEnvironment.with_candlepin_name(name, organization: @host.organization) - end - end - if cve_params[:content_view_environment_ids].present? - new_cves = cve_params[:content_view_environment_ids].map do |id| - ::Katello::ContentViewEnvironment.find_by(id: id) - end - end + cves = ::Katello::ContentViewEnvironment.fetch_content_view_environments( + labels: cve_params[:content_view_environments], + ids: cve_params[:content_view_environment_ids], + organization: @organization || @host&.organization) - @host.content_facet.content_view_environments = new_cves.compact if new_cves.present? + @host.content_facet.content_view_environments = cves if cves.present? end def cve_params diff --git a/app/controllers/katello/concerns/authorization/api/v2/content_views_controller.rb b/app/controllers/katello/concerns/authorization/api/v2/content_views_controller.rb index ccd02b28763..44966e9acc0 100644 --- a/app/controllers/katello/concerns/authorization/api/v2/content_views_controller.rb +++ b/app/controllers/katello/concerns/authorization/api/v2/content_views_controller.rb @@ -135,7 +135,7 @@ def authorize_remove_environments(view, options) # rubocop:disable Metrics/Cyclo end end - if Katello::ActivationKey.where(:content_view_id => view, :environment_id => env_ids).count > 0 + if Katello::ActivationKey.in_content_views_and_environments(content_views: view.id, lifecycle_environments: env_ids).count > 0 # if we are reassigning activation key environments/ cv # make sure the activation key using present environments or cv are editable. unless options[:key_content_view_id] && options[:key_environment_id] diff --git a/app/lib/actions/katello/activation_key/reassign.rb b/app/lib/actions/katello/activation_key/reassign.rb index 99929e68012..4feeb830252 100644 --- a/app/lib/actions/katello/activation_key/reassign.rb +++ b/app/lib/actions/katello/activation_key/reassign.rb @@ -3,9 +3,10 @@ module Katello module ActivationKey class Reassign < Actions::Base def plan(activation_key, content_view_id, environment_id) - activation_key.content_view_id = content_view_id - activation_key.environment_id = environment_id - activation_key.save! + activation_key.assign_single_environment( + content_view: ::Katello::ContentView.find(content_view_id), + lifecycle_environment: ::Katello::KTEnvironment.find(environment_id) + ) end end end diff --git a/app/lib/actions/katello/content_view/remove.rb b/app/lib/actions/katello/content_view/remove.rb index e56f4dc727e..f384de5069f 100644 --- a/app/lib/actions/katello/content_view/remove.rb +++ b/app/lib/actions/katello/content_view/remove.rb @@ -119,11 +119,13 @@ def validate_options(_content_view, cv_envs, versions, options) end all_cv_envs = combined_cv_envs(cv_envs, versions) - if all_cv_envs.flat_map(&:hosts).any? && system_cve(options).nil? + if all_cv_envs.flat_map(&:hosts).any? && !cve_exists?(options[:system_environment_id], + options[:system_content_view_id]) fail _("Unable to reassign systems. Please check system_content_view_id and system_environment_id.") end - if all_cv_envs.flat_map(&:activation_keys).any? && activation_key_cve(options).nil? + if all_cv_envs.flat_map(&:activation_keys).any? && !cve_exists?(options[:key_environment_id], + options[:key_content_view_id]) fail _("Unable to reassign activation_keys. Please check activation_key_content_view_id and activation_key_environment_id.") end end @@ -132,16 +134,10 @@ def combined_cv_envs(cv_envs, versions) (cv_envs + versions.flat_map(&:content_view_environments)).uniq end - def system_cve(options) - ::Katello::ContentViewEnvironment.where(:environment_id => options[:system_environment_id], - :content_view_id => options[:system_content_view_id] - ).first - end - - def activation_key_cve(options) - ::Katello::ContentViewEnvironment.where(:environment_id => options[:key_environment_id], - :content_view_id => options[:key_content_view_id] - ).first + def cve_exists?(environment_id, content_view_id) + ::Katello::ContentViewEnvironment.where(:environment_id => environment_id, + :content_view_id => content_view_id + ).exists? end end end diff --git a/app/lib/katello/util/cveak_migrator.rb b/app/lib/katello/util/cveak_migrator.rb new file mode 100644 index 00000000000..ed2a5cf78a7 --- /dev/null +++ b/app/lib/katello/util/cveak_migrator.rb @@ -0,0 +1,37 @@ +module Katello + module Util + class FakeActivationKey < ApplicationRecord + self.table_name = 'katello_activation_keys' + end + + class CVEAKMigrator # used in db/migrate/20220929204746_add_content_view_environment_content_facet.rb + def execute! + aks_with_no_cve = [] + aks_with_missing_cve = [] + + FakeActivationKey.all.each do |ak| + if ::Katello::ContentView.exists?(id: ak.content_view_id) && ::Katello::KTEnvironment.exists?(ak.environment_id) + cve = ::Katello::ContentViewEnvironment.find_by(content_view_id: ak.content_view_id, environment_id: ak.environment_id) + if cve.blank? + aks_with_no_cve << ak + end + else + aks_with_missing_cve << ak + end + end + + if aks_with_missing_cve.present? || aks_with_no_cve.present? + Rails.logger.warn "Found #{aks_with_no_cve.count} activation keys whose lifecycle environment does not have a corresponding ContentViewEnvironment" + Rails.logger.warn "Found #{aks_with_missing_cve.count} activation keys whose content facet is missing either content_view_id or lifecycle_environment_id" + Rails.logger.info "You may want to change the content view / lifecycle environment for these activation keys manually." + end + (aks_with_no_cve + aks_with_missing_cve).each do |ak| + default_content_view = ak.organization.default_content_view + library = ak.organization.library + Rails.logger.info "Updating activation key #{ak.name} with default content_view_id and lifecycle_environment_id" + ak&.update_columns(content_view_id: default_content_view&.id, environment_id: library&.id) + end + end + end + end +end diff --git a/app/models/katello/activation_key.rb b/app/models/katello/activation_key.rb index f4ff2535931..8b0c2ef29d6 100644 --- a/app/models/katello/activation_key.rb +++ b/app/models/katello/activation_key.rb @@ -7,11 +7,16 @@ class ActivationKey < Katello::Model include ForemanTasks::Concerns::ActionSubject include ScopedSearchExtensions + has_many :content_view_environment_activation_keys, :class_name => "Katello::ContentViewEnvironmentActivationKey", + :dependent => :destroy, :inverse_of => :activation_key + has_many :content_view_environments, :through => :content_view_environment_activation_keys, + :class_name => "Katello::ContentViewEnvironment", :source => :content_view_environment + + has_many :content_views, :through => :content_view_environments, :class_name => "Katello::ContentView" + has_many :lifecycle_environments, :through => :content_view_environments, :class_name => "Katello::KTEnvironment" + belongs_to :organization, :inverse_of => :activation_keys - belongs_to :environment, :class_name => "KTEnvironment", :inverse_of => :activation_keys belongs_to :user, :inverse_of => :activation_keys, :class_name => "::User" - belongs_to :content_view, :class_name => "Katello::ContentView", :inverse_of => :activation_keys - has_many :key_host_collections, :class_name => "Katello::KeyHostCollection", :dependent => :destroy has_many :host_collections, :through => :key_host_collections @@ -25,9 +30,6 @@ class ActivationKey < Katello::Model has_many :activation_key_purpose_addons, :class_name => "Katello::ActivationKeyPurposeAddon", :dependent => :destroy, :inverse_of => :activation_key has_many :purpose_addons, :class_name => "Katello::PurposeAddon", :through => :activation_key_purpose_addons - alias_method :lifecycle_environment, :environment - - before_validation :set_default_content_view, :unless => :persisted? before_destroy :validate_destroyable! accepts_nested_attributes_for :purpose_addons @@ -36,7 +38,6 @@ class ActivationKey < Katello::Model validates :name, :presence => true validates :name, :format => { without: /,/, message: _('cannot contain commas') } validates :name, :uniqueness => {:scope => :organization_id} - validate :environment_exists validates :max_hosts, :numericality => {:less_than => 2**31, :allow_nil => true} validates_each :max_hosts do |record, attr, value| if record.unlimited_hosts @@ -54,15 +55,16 @@ class ActivationKey < Katello::Model end end end - validates_with Validators::ContentViewEnvironmentValidator - - scope :in_environment, ->(env) { where(:environment_id => env) } scoped_search :on => :name, :complete_value => true scoped_search :on => :organization_id, :complete_value => true, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER - scoped_search :rename => :environment, :on => :name, :relation => :environment, :complete_value => true - scoped_search :rename => :content_view, :on => :name, :relation => :content_view, :complete_value => true - scoped_search :on => :content_view_id, :complete_value => true, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER + + scoped_search :relation => :content_views, :on => :name, :complete_value => true, :rename => :content_view, :only_explicit => true + scoped_search :relation => :content_views, :on => :id, :complete_value => true, :rename => :content_view_id, :only_explicit => true + + scoped_search :relation => :lifecycle_environments, :on => :id, :complete_value => true, :rename => :lifecycle_environment_id, :only_explicit => true + scoped_search :relation => :lifecycle_environments, :on => :name, :complete_value => true, :rename => :environment, :only_explicit => true + scoped_search :on => :description, :complete_value => true scoped_search :on => :name, :relation => :subscriptions, :rename => :subscription_name, :complete_value => true, :ext_method => :find_by_subscription_name scoped_search :on => :id, :relation => :subscriptions, :rename => :subscription_id, :complete_value => true, @@ -71,12 +73,84 @@ class ActivationKey < Katello::Model scoped_search :on => :purpose_role, :rename => :role, :complete_value => true scoped_search :on => :name, :rename => :addon, :relation => :purpose_addon, :complete_value => true, :ext_method => :find_by_purpose_addons - def environment_exists - if environment_id && environment.nil? - errors.add(:environment, _("ID: %s doesn't exist ") % environment_id) - elsif !environment.nil? && environment.organization != self.organization - errors.add(:environment, _("name: %s doesn't exist ") % environment.name) + def content_view_environments=(new_cves) + if new_cves.length > 1 && !Setting['allow_multiple_content_views'] + fail ::Katello::Errors::MultiEnvironmentNotSupportedError, + _("Assigning an activation key to multiple content view environments is not enabled.") + end + super(new_cves) + Katello::ContentViewEnvironmentActivationKey.reprioritize_for_activation_key(self, new_cves) + self.content_view_environments.reload unless self.new_record? + end + + def multi_content_view_environment? + # returns false if there are no content view environments + content_view_environments.size > 1 + end + + def single_content_view_environment? + # also returns false if there are no content view environments + content_view_environments.size == 1 + end + + def single_content_view + if multi_content_view_environment? + Rails.logger.warn _("Activation key %s has more than one content view. Use #content_views instead.") % name + end + content_view_environments&.first&.content_view + end + + def content_view + single_content_view + end + + def environment + single_lifecycle_environment + end + + def single_lifecycle_environment + if multi_content_view_environment? + Rails.logger.warn _("Activation key %s has more than one lifecycle environment. Use #lifecycle_environments instead.") % name + end + content_view_environments&.first&.lifecycle_environment + end + + # rubocop:disable Metrics/CyclomaticComplexity + def assign_single_environment( + content_view_id: nil, lifecycle_environment_id: nil, environment_id: nil, + content_view: nil, lifecycle_environment: nil, environment: nil + ) + lifecycle_environment_id ||= environment_id || lifecycle_environment&.id || environment&.id || self.single_lifecycle_environment&.id + content_view_id ||= content_view&.id || self.single_content_view&.id + + unless lifecycle_environment_id + fail _("Lifecycle environment must be specified") + end + + unless content_view_id + fail _("Content view must be specified") + end + + content_view_environment = ::Katello::ContentViewEnvironment + .where(:content_view_id => content_view_id, :environment_id => lifecycle_environment_id) + .first_or_create do |cve| + Rails.logger.info("ContentViewEnvironment not found for content view '#{cve.content_view_name}' and environment '#{cve.environment&.name}'; creating a new one.") end + fail _("Unable to create ContentViewEnvironment. Check the logs for more information.") if content_view_environment.nil? + + self.content_view_environments = [content_view_environment] + end + + def self.in_environments(lifecycle_environments) + in_content_views_and_environments(:lifecycle_environments => lifecycle_environments) + end + + def self.in_content_views_and_environments(content_views: nil, lifecycle_environments: nil, content_view_environments: nil) + relation = self.joins(:content_view_environment_activation_keys => :content_view_environment) + relation = relation.where("#{::Katello::ContentViewEnvironment.table_name}.content_view_id" => content_views) if content_views + relation = relation.where("#{::Katello::ContentViewEnvironment.table_name}.environment_id" => lifecycle_environments) if lifecycle_environments + relation = relation.where("#{::Katello::ContentViewEnvironment.table_name}.id" => content_view_environments) if content_view_environments + relation end def usage_count @@ -92,11 +166,11 @@ def related_resources end def available_releases - if self.environment - self.environment.available_releases - else - self.organization.library.available_releases + releases = self.content_view_environments.flat_map do |cve| + cve.content_view.version(cve.lifecycle_environment).available_releases end + return self.organization.library.available_releases if releases.blank? + releases end def available_subscriptions @@ -133,8 +207,9 @@ def calculate_consumption(product, pools, _allocate) def copy(new_name) new_key = ActivationKey.new new_key.name = new_name - new_key.attributes = self.attributes.slice("description", "environment_id", "organization_id", "content_view_id", "max_hosts", "unlimited_hosts") + new_key.attributes = self.attributes.slice("description", "organization_id", "max_hosts", "unlimited_hosts") new_key.host_collection_ids = self.host_collection_ids + new_key.content_view_environments = content_view_environments new_key end @@ -190,14 +265,6 @@ def validate_destroyable! true end - private - - def set_default_content_view - if self.environment && self.content_view.nil? - self.content_view = self.environment.try(:default_content_view) - end - end - apipie :class, desc: "A class representing #{model_name.human} object" do name 'Activation Key' refs 'ActivationKey' diff --git a/app/models/katello/authorization/activation_key.rb b/app/models/katello/authorization/activation_key.rb index 67fc00575b1..22b48dc2058 100644 --- a/app/models/katello/authorization/activation_key.rb +++ b/app/models/katello/authorization/activation_key.rb @@ -34,7 +34,7 @@ def any_editable? end def all_editable?(content_view, environments) - key_query = ActivationKey.where(:content_view_id => content_view, :environment_id => environments) + key_query = ActivationKey.in_content_views_and_environments(content_views: content_view, lifecycle_environments: environments) key_query.count == key_query.editable.count end end diff --git a/app/models/katello/authorization/content_view_environment.rb b/app/models/katello/authorization/content_view_environment.rb index 1eca249f22a..d165e8736a9 100644 --- a/app/models/katello/authorization/content_view_environment.rb +++ b/app/models/katello/authorization/content_view_environment.rb @@ -5,5 +5,12 @@ module Authorization::ContentViewEnvironment def readable? self.content_view.readable? && self.environment.readable? end + + module ClassMethods + def readable + where(:content_view_id => ::Katello::ContentView.readable, + :environment_id => ::Katello::KTEnvironment.readable) + end + end end end diff --git a/app/models/katello/content_view.rb b/app/models/katello/content_view.rb index ac8ed8e7d5c..9ce9d35410c 100644 --- a/app/models/katello/content_view.rb +++ b/app/models/katello/content_view.rb @@ -38,8 +38,6 @@ class ContentView < Katello::Model has_many :filters, :dependent => :destroy, :class_name => "Katello::ContentViewFilter" - has_many :activation_keys, :class_name => "Katello::ActivationKey", :dependent => :restrict_with_exception - has_many :content_view_environment_content_facets, :class_name => "Katello::ContentViewEnvironmentContentFacet", :through => :content_view_environments has_many :content_facets, :class_name => "Katello::Host::ContentFacet", :through => :content_view_environment_content_facets, @@ -47,6 +45,11 @@ class ContentView < Katello::Model has_many :hosts, :class_name => "::Host::Managed", :through => :content_facets, :inverse_of => :content_views + has_many :content_view_environment_activation_keys, :class_name => "Katello::ContentViewEnvironmentActivationKey", + :through => :content_view_environments + has_many :activation_keys, :class_name => "Katello::ActivationKey", :through => :content_view_environment_activation_keys, + :inverse_of => :content_views + has_many :hostgroup_content_facets, :class_name => "Katello::Hostgroup::ContentFacet", :inverse_of => :content_view, :dependent => :nullify has_many :hostgroups, :class_name => "::Hostgroup", :through => :hostgroup_content_facets, @@ -720,7 +723,7 @@ def check_remove_from_environment!(env) } dependencies.each do |key, name| - if (models = self.association(key).scope.in_environment(env)).any? + if (models = self.association(key).scope.in_environments(env)).any? errors << _("Cannot remove '%{view}' from environment '%{env}' due to associated %{dependent}: %{names}.") % { view: self.name, env: env.name, dependent: name, names: models.map(&:name).join(", ") } end diff --git a/app/models/katello/content_view_environment.rb b/app/models/katello/content_view_environment.rb index 3e1905da0dc..369e26f0146 100644 --- a/app/models/katello/content_view_environment.rb +++ b/app/models/katello/content_view_environment.rb @@ -15,6 +15,9 @@ class ContentViewEnvironment < Katello::Model has_many :content_view_environment_content_facets, :class_name => "Katello::ContentViewEnvironmentContentFacet", :dependent => :destroy, :inverse_of => :content_view_environment has_many :content_facets, through: :content_view_environment_content_facets, :class_name => "::Katello::Host::ContentFacet", :inverse_of => :content_view_environments + has_many :content_view_environment_activation_keys, :class_name => "Katello::ContentViewEnvironmentActivationKey", :dependent => :destroy, :inverse_of => :content_view_environment + has_many :activation_keys, through: :content_view_environment_activation_keys, :class_name => "::Katello::ActivationKey", :inverse_of => :content_view_environments + validates_lengths_from_database validates :environment_id, uniqueness: {scope: :content_view_id}, presence: true validates :content_view_id, presence: true @@ -26,11 +29,16 @@ class ContentViewEnvironment < Katello::Model scope :non_default, -> { joins(:content_view).where("katello_content_views.default" => false) } scope :default, -> { joins(:content_view).where("katello_content_views.default" => true) } alias :lifecycle_environment :environment + delegate :organization, :to => :environment def self.for_content_facets(content_facets) joins(:content_view_environment_content_facets, :content_facets).where("#{Katello::ContentViewEnvironmentContentFacet.table_name}.content_facet_id" => content_facets).uniq end + def self.for_activation_keys(activation_keys) + joins(:content_view_environment_activation_keys, :activation_keys).where("#{Katello::ContentViewEnvironmentActivationKey.table_name}.activation_key_id" => activation_keys).uniq + end + def self.with_candlepin_name(cp_name, organization: Organization.current) lce_name, cv_name = cp_name.split('/') if cv_name.blank? && lce_name == 'Library' @@ -51,7 +59,7 @@ def hosts end def activation_keys - content_view.activation_keys.in_environment(environment) + ::Katello::ActivationKey.in_content_views_and_environments(:content_views => self.content_view, :lifecycle_environments => self.environment) end def default_environment? @@ -63,8 +71,29 @@ def candlepin_name "#{environment.label}/#{content_view.label}" end - def priority(content_facet) - content_view_environment_content_facets.find_by(:content_facet_id => content_facet.id).try(:priority) + def priority(content_object) + if content_object.is_a? Katello::ActivationKey + content_view_environment_activation_keys.find_by(:activation_key_id => content_object.id).try(:priority) + elsif content_object.is_a? Katello::Host::ContentFacet + content_view_environment_content_facets.find_by(:content_facet_id => content_object.id).try(:priority) + end + end + + def self.fetch_content_view_environments(labels: [], ids: [], organization:) + # Must do maps here to ensure CVEs remain in the same order. + # Using ActiveRecord .where will return them in a different order. + if ids.present? + ids.map! do |id| + ::Katello::ContentViewEnvironment.find_by(id: id) + end + ids.compact + elsif labels.present? + environment_names = labels.map(&:strip) + environment_names.map! do |name| + with_candlepin_name(name, organization: organization) + end + environment_names.compact + end end private diff --git a/app/models/katello/content_view_environment_activation_key.rb b/app/models/katello/content_view_environment_activation_key.rb new file mode 100644 index 00000000000..3d8d6901e20 --- /dev/null +++ b/app/models/katello/content_view_environment_activation_key.rb @@ -0,0 +1,20 @@ +module Katello + class ContentViewEnvironmentActivationKey < Katello::Model + belongs_to :content_view_environment, :class_name => "::Katello::ContentViewEnvironment", :inverse_of => :content_view_environment_activation_keys + belongs_to :activation_key, :class_name => "::Katello::ActivationKey", :inverse_of => :content_view_environment_activation_keys + + default_scope { order(:priority => :asc) } + + validates :content_view_environment_id, presence: true + validates :activation_key_id, presence: true, unless: :new_record? + + def self.reprioritize_for_activation_key(activation_key, new_cves) + new_order = new_cves.map do |cve| + activation_key.content_view_environment_activation_keys.find_by(:content_view_environment_id => cve.id) + end + new_order.compact.each_with_index do |cveak, index| + cveak.update_column(:priority, index) + end + end + end +end diff --git a/app/models/katello/kt_environment.rb b/app/models/katello/kt_environment.rb index a6de606fa64..9dc5cf77503 100644 --- a/app/models/katello/kt_environment.rb +++ b/app/models/katello/kt_environment.rb @@ -8,8 +8,6 @@ class KTEnvironment < Katello::Model include Ext::LabelFromName belongs_to :organization, :class_name => "Organization", :inverse_of => :kt_environments - has_many :activation_keys, :class_name => "Katello::ActivationKey", - :dependent => :restrict_with_exception, :foreign_key => :environment_id 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 @@ -30,14 +28,21 @@ class KTEnvironment < Katello::Model 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", - :inverse_of => :lifecycle_environment + :class_name => "Katello::ContentViewEnvironmentContentFacet" + has_many :content_facets, :through => :content_view_environment_content_facets, - :class_name => "Katello::Host::ContentFacet", - :inverse_of => :lifecycle_environments + :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, diff --git a/app/services/katello/product_content_finder.rb b/app/services/katello/product_content_finder.rb index ceb1eb99558..cb4ffaa0d1c 100644 --- a/app/services/katello/product_content_finder.rb +++ b/app/services/katello/product_content_finder.rb @@ -15,14 +15,7 @@ def initialize(params = {}) def product_content if match_environment - if consumable.is_a?(::Katello::ActivationKey) # TODO: remove this when AKs are refactored for multi CV - environment = consumable.lifecycle_environment - view = consumable.content_view - return ProductContent.none unless environment && view - versions = ContentViewVersion.in_environment(environment).where(:content_view_id => view).first - else # consumable is a SubscriptionFacet - versions = consumable.content_view_environments.select(:content_view_version_id).map(&:content_view_version_id) - end + versions = consumable.content_view_environments.select(:content_view_version_id).map(&:content_view_version_id) end considered_products = match_subscription ? consumable.products : consumable.organization.products.enabled.uniq diff --git a/app/services/katello/registration_manager.rb b/app/services/katello/registration_manager.rb index 0ea9a7efa46..9cba78bc02a 100644 --- a/app/services/katello/registration_manager.rb +++ b/app/services/katello/registration_manager.rb @@ -166,7 +166,7 @@ def register_host(host, consumer_params, content_view_environments, activation_k if activation_keys.present? if content_view_environments.blank? - content_view_environments = [lookup_content_view_environment(activation_keys)] + content_view_environments = lookup_content_view_environments(activation_keys) end set_host_collections(host, activation_keys) end @@ -264,12 +264,20 @@ def set_host_collections(host, activation_keys) host.host_collection_ids = host_collection_ids end - def lookup_content_view_environment(activation_keys) + def lookup_content_view_environments(activation_keys) + if Setting['allow_multiple_content_views'] + cves = activation_keys.reverse.map do |act_key| + act_key.content_view_environments + end + fail _('At least one activation key must have a lifecycle environment and content view assigned to it') if cves.blank? + return cves.flatten.uniq + end + activation_key = activation_keys.reverse.detect do |act_key| - act_key.environment && act_key.content_view + act_key.content_view_environments.any? end if activation_key - ::Katello::ContentViewEnvironment.where(:content_view_id => activation_key.content_view, :environment_id => activation_key.environment).first + ::Katello::ContentViewEnvironment.where(:content_view_id => activation_key.content_view, :environment_id => activation_key.environment) else fail _('At least one activation key must have a lifecycle environment and content view assigned to it') end diff --git a/app/views/katello/api/v2/activation_keys/base.json.rabl b/app/views/katello/api/v2/activation_keys/base.json.rabl index b53355bffde..6fb558c68f6 100644 --- a/app/views/katello/api/v2/activation_keys/base.json.rabl +++ b/app/views/katello/api/v2/activation_keys/base.json.rabl @@ -3,16 +3,29 @@ extends 'katello/api/v2/common/timestamps' attributes :id, :name, :description, :unlimited_hosts, :auto_attach -attributes :content_view_id +node :content_view_id do |ak| + ak.single_content_view&.id +end + +node :content_view do |ak| + ak.single_content_view&.slice(:id, :name) +end -child :content_view => :content_view do +child :content_views => :content_views do attributes :id, :name end -child :environment => :environment do - attributes :name, :id +node :environment_id do |ak| + ak.single_lifecycle_environment&.id +end + +node :environment do |ak| + ak.single_lifecycle_environment&.slice(:id, :name) +end + +child :lifecycle_environments => :lifecycle_environments do + attributes :id, :name end -attributes :environment_id attributes :usage_count, :user_id, :max_hosts, :system_template_id, :release_version, :purpose_usage, :purpose_role diff --git a/app/views/katello/api/v2/content_view_versions/base.json.rabl b/app/views/katello/api/v2/content_view_versions/base.json.rabl index 7947def939e..02635382db4 100644 --- a/app/views/katello/api/v2/content_view_versions/base.json.rabl +++ b/app/views/katello/api/v2/content_view_versions/base.json.rabl @@ -70,7 +70,7 @@ child :environments => :environments do end node :activation_key_count do |env| - Katello::ActivationKey.where(:environment_id => env.id).where(:content_view_id => version.content_view_id).count + Katello::ActivationKey.in_content_views_and_environments(lifecycle_environments: env, content_views: version.content_view).count end end diff --git a/app/views/katello/api/v2/content_views/base.json.rabl b/app/views/katello/api/v2/content_views/base.json.rabl index 975caa4331f..31243481ed3 100644 --- a/app/views/katello/api/v2/content_views/base.json.rabl +++ b/app/views/katello/api/v2/content_views/base.json.rabl @@ -44,7 +44,7 @@ node :environments do |cv| id: env.id, label: env.label, name: env.name, - activation_keys: cv&.activation_keys&.in_environment(env)&.ids, + activation_keys: cv&.activation_keys&.in_environments([env])&.ids, hosts: cv&.hosts&.in_environments([env])&.ids, permissions: {readable: env.readable?} } diff --git a/db/migrate/20240730163043_add_content_view_environment_activation_key.rb b/db/migrate/20240730163043_add_content_view_environment_activation_key.rb new file mode 100644 index 00000000000..e4074bc4472 --- /dev/null +++ b/db/migrate/20240730163043_add_content_view_environment_activation_key.rb @@ -0,0 +1,85 @@ +class AddContentViewEnvironmentActivationKey < ActiveRecord::Migration[6.1] + class FakeActivationKey < ApplicationRecord + self.table_name = 'katello_activation_keys' + end + + def up + create_table :katello_content_view_environment_activation_keys do |t| + t.references :content_view_environment, :null => false, :index => false, :foreign_key => { :to_table => 'katello_content_view_environments' } + t.references :activation_key, :null => false, :index => false, :foreign_key => { :to_table => 'katello_activation_keys' } + end + ::Katello::Util::CVEAKMigrator.new.execute! + FakeActivationKey.all.each do |activation_key| + cve_id = ::Katello::KTEnvironment.find(activation_key.environment_id) + .content_view_environments + .find_by(content_view_id: activation_key.content_view_id) + &.id + unless cve_id.present? && ::Katello::ContentViewEnvironmentActivationKey.create( + activation_key_id: activation_key.id, + content_view_environment_id: cve_id + ) + Rails.logger.warn "Failed to create ContentViewEnvironmentActivationKey for activation_key #{activation_key.id}" + end + end + + remove_column :katello_activation_keys, :content_view_id + remove_column :katello_activation_keys, :environment_id + end + + def down + add_column :katello_activation_keys, :content_view_id, :integer, :index => true + add_column :katello_activation_keys, :environment_id, :integer, :index => true + + add_foreign_key "katello_activation_keys", "katello_content_views", + :name => "katello_activation_keys_content_view_id", :column => "content_view_id" + + add_foreign_key "katello_activation_keys", "katello_environments", + :name => "katello_activation_keys_environment_id", :column => "environment_id" + + ::Katello::ActivationKey.reset_column_information + + ::Katello::ContentViewEnvironmentActivationKey.all.each do |cvecf| + activation_key = cvecf.activation_key + cve = cvecf.content_view_environment + default_org = cve.environment&.organization + default_cv_id = default_org&.default_content_view&.id + default_lce_id = default_org&.library&.id + cv_id = cvecf.content_view_environment.content_view_id || default_cv_id + lce_id = cvecf.content_view_environment.environment_id || default_lce_id + say "Updating activation_key #{activation_key.id} with cv_id #{cv_id} and lce_id #{lce_id}" + activation_key.content_view_id = cv_id + activation_key.environment_id = lce_id + activation_key.save(validate: false) + end + + ensure_no_null_cv_lce + change_column :katello_activation_keys, :content_view_id, :integer, :null => false + change_column :katello_activation_keys, :lifecycle_environment_id, :integer, :null => false + + drop_table :katello_content_view_environment_activation_keys + end + + def ensure_no_null_cv_lce + # The following is to try to prevent PG::NotNullViolation: ERROR: column "content_view_id" contains null values + # since we add null constraints to the columns in the next step + activation_keys_without_cv = ::Katello::ActivationKey.where(content_view_id: nil) + if activation_keys_without_cv.any? + say "Found #{activation_keys_without_cv.count} activation_keys with nil content_view_id" + activation_keys_without_cv.each do |activation_key| + say "reassigning bad activation_key #{activation_key.id} to default content view" + activation_key.content_view_id = activation_key&.organization&.default_content_view&.id + activation_key.save(validate: false) + end + end + + activation_keys_without_lce = ::Katello::ActivationKey.where(environment_id: nil) + if activation_keys_without_lce.any? + say "Found #{activation_keys_without_lce.count} activation_keys with nil environment_id" + activation_keys_without_lce.each do |activation_key| + say "reassigning bad activation_key #{activation_key.id} to default lifecycle environment" + activation_key.environment_id = activation_key&.organization&.library&.id + activation_key.save(validate: false) + end + end + end +end diff --git a/db/migrate/20240903194428_add_priority_to_content_view_environment_activation_key.rb b/db/migrate/20240903194428_add_priority_to_content_view_environment_activation_key.rb new file mode 100644 index 00000000000..2761b4ad1fa --- /dev/null +++ b/db/migrate/20240903194428_add_priority_to_content_view_environment_activation_key.rb @@ -0,0 +1,5 @@ +class AddPriorityToContentViewEnvironmentActivationKey < ActiveRecord::Migration[6.1] + def change + add_column :katello_content_view_environment_activation_keys, :priority, :integer, default: 0, null: false + end +end diff --git a/spec/models/activation_key_spec.rb b/spec/models/activation_key_spec.rb index 33abac85a00..d7ab93655da 100644 --- a/spec/models/activation_key_spec.rb +++ b/spec/models/activation_key_spec.rb @@ -14,16 +14,20 @@ module Katello @organization = get_organization @environment_1 = katello_environments(:dev) @environment_2 = katello_environments(:staging) + + @library_cve = katello_content_view_environments(:library_default_view_environment) + @dev_cve = katello_content_view_environments(:library_dev_view_dev) + @staging_cve = katello_content_view_environments(:library_dev_staging_view_staging) + @akey = ActivationKey.create(:name => aname, :description => adesc, :organization => @organization, - :environment_id => @environment_1.id, :unlimited_hosts => false, + :unlimited_hosts => false, :max_hosts => 1) end describe "in valid state" do it "should be valid if the environment is Library" do @akey.name = 'valid key' - @akey.environment_id = @organization.library.id - @akey.content_view_id = @organization.library.content_views.first.id + @akey.content_view_environments = [@library_cve] value(@akey).must_be :valid? value(@akey.errors[:base]).must_be_empty end @@ -43,15 +47,8 @@ module Katello value(@akey.errors[:base]).must_be_empty end - it "should be invalid if non-existent environment is specified" do - @akey.name = 'invalid key' - @akey.environment_id = 123_456 - - value(@akey).wont_be :valid? - value(@akey.errors[:environment]).wont_be_empty - end - it "should be invalid if environment in another org is specified" do + skip "TODO - should be in CVE" org_2 = get_organization(:organization2) #Organization.create!(:name=>'test_org2', :label=> 'test_org2') env_1_org2 = KTEnvironment.create(:name => 'dev', :label => 'dev', :prior => org_2.library.id, :organization => org_2) @@ -87,8 +84,8 @@ module Katello it "environment" do a = ActivationKey.find_by_name(aname) value(a).wont_be :nil? - b = ActivationKey.update(a.id, :environment => @environment_2) - value(b.environment).must_equal @environment_2 + b = ActivationKey.update(a.id, content_view_environments: [@staging_cve]) + value(b.content_view_environments.first).must_equal @staging_cve end end diff --git a/test/controllers/api/v2/activation_keys_controller_test.rb b/test/controllers/api/v2/activation_keys_controller_test.rb index c8ba442c104..26040cec028 100644 --- a/test/controllers/api/v2/activation_keys_controller_test.rb +++ b/test/controllers/api/v2/activation_keys_controller_test.rb @@ -91,7 +91,7 @@ def test_create_protected_object def test_create_protected dev_env_read_permission = {:name => :view_lifecycle_environments, :search => "id=\"#{@library.id}\"" } - view_read_permission = {:name => :view_lifecycle_environments, :search => "id=\"#{@view.id}\"" } + view_read_permission = {:name => :view_content_views, :search => "label=\"#{@view.label}\"" } allowed_perms = [[@create_permission, dev_env_read_permission, view_read_permission]] denied_perms = [@view_permission, @update_permission, @destroy_permission] diff --git a/test/controllers/api/v2/content_views_controller_test.rb b/test/controllers/api/v2/content_views_controller_test.rb index fa75d933687..4cef3e553b7 100644 --- a/test/controllers/api/v2/content_views_controller_test.rb +++ b/test/controllers/api/v2/content_views_controller_test.rb @@ -481,8 +481,8 @@ def test_remove_protected ] env_ids = [@dev.id.to_s, @staging.id.to_s] - Katello::ActivationKey.expects(:where).at_least_once.returns([]).with do |args| - args[:content_view_id].id == @library_dev_staging_view.id && args[:environment_id] == env_ids + Katello::ActivationKey.expects(:in_content_views_and_environments).at_least_once.returns([]).with do |args| + args[:content_views] == @library_dev_staging_view.id && args[:lifecycle_environments] == env_ids end assert_protected_action(:remove, allowed_perms, denied_perms) do @@ -558,8 +558,8 @@ def test_remove_protected_envs_with_host env_ids = [environment.id.to_s] - Katello::ActivationKey.expects(:where).at_least_once.returns([]).with do |args| - args[:content_view_id].id == content_view.id && args[:environment_id] == env_ids + Katello::ActivationKey.expects(:in_content_views_and_environments).at_least_once.returns([]).with do |args| + args[:content_views] == content_view.id && args[:lifecycle_environments] == env_ids end assert_protected_action(:remove, allowed_perms, denied_perms) do diff --git a/test/fixtures/models/katello_activation_keys.yml b/test/fixtures/models/katello_activation_keys.yml index a20cc2e62fd..580350f8664 100644 --- a/test/fixtures/models/katello_activation_keys.yml +++ b/test/fixtures/models/katello_activation_keys.yml @@ -2,32 +2,24 @@ simple_key: name: Simple Activation Key description: A simple activation key. organization_id: <%= ActiveRecord::FixtureSet.identify(:empty_organization) %> - environment_id: <%= ActiveRecord::FixtureSet.identify(:library) %> - content_view_id: <%= ActiveRecord::FixtureSet.identify(:acme_default) %> auto_attach: true another_simple_key: name: Another Simple Activation Key description: Another simple activation key. organization_id: <%= ActiveRecord::FixtureSet.identify(:empty_organization) %> - environment_id: <%= ActiveRecord::FixtureSet.identify(:library) %> - content_view_id: <%= ActiveRecord::FixtureSet.identify(:acme_default) %> auto_attach: true yet_another_simple_key: name: Yet Another Activation Key description: And another simple activation key! organization_id: <%= ActiveRecord::FixtureSet.identify(:empty_organization) %> - environment_id: <%= ActiveRecord::FixtureSet.identify(:library) %> - content_view_id: <%= ActiveRecord::FixtureSet.identify(:acme_default) %> auto_attach: true and_yet_another_simple_key: name: And Yet Another Simple Activation Key description: Here we go again! organization_id: <%= ActiveRecord::FixtureSet.identify(:empty_organization) %> - environment_id: <%= ActiveRecord::FixtureSet.identify(:library) %> - content_view_id: <%= ActiveRecord::FixtureSet.identify(:acme_default) %> auto_attach: true dev_key: @@ -35,24 +27,19 @@ dev_key: description: A simple activation key. created_at: <%= Time.now %> updated_at: <%= Time.now %> - organization_id: <%= ActiveRecord::FixtureSet.identify(:empty_organization) %> - environment_id: <%= ActiveRecord::FixtureSet.identify(:dev) %> auto_attach: true + organization_id: <%= ActiveRecord::FixtureSet.identify(:empty_organization) %> library_dev_staging_view_key: name: Lib Dev Stating Activation Key description: Here we go again! organization_id: <%= ActiveRecord::FixtureSet.identify(:empty_organization) %> - environment_id: <%= ActiveRecord::FixtureSet.identify(:dev) %> - content_view_id: <%= ActiveRecord::FixtureSet.identify(:library_dev_staging_view) %> auto_attach: true purpose_attributes_key: name: Activation Key description: A simple activation key. organization_id: <%= ActiveRecord::FixtureSet.identify(:empty_organization) %> - environment_id: <%= ActiveRecord::FixtureSet.identify(:library) %> - content_view_id: <%= ActiveRecord::FixtureSet.identify(:acme_default) %> auto_attach: true purpose_role: role name purpose_usage: usage name diff --git a/test/fixtures/models/katello_content_view_environment_activation_keys.yml b/test/fixtures/models/katello_content_view_environment_activation_keys.yml new file mode 100644 index 00000000000..a77d6672873 --- /dev/null +++ b/test/fixtures/models/katello_content_view_environment_activation_keys.yml @@ -0,0 +1,26 @@ +simple_key_cveak: + content_view_environment_id: <%= ActiveRecord::FixtureSet.identify(:library_default_view_environment) %> + activation_key_id: <%= ActiveRecord::FixtureSet.identify(:simple_key) %> + +another_simple_key_cveak: + content_view_environment_id: <%= ActiveRecord::FixtureSet.identify(:library_default_view_environment) %> + activation_key_id: <%= ActiveRecord::FixtureSet.identify(:another_simple_key) %> + +yet_another_simple_key_cveak: + content_view_environment_id: <%= ActiveRecord::FixtureSet.identify(:library_default_view_environment) %> + activation_key_id: <%= ActiveRecord::FixtureSet.identify(:yet_another_simple_key) %> + +and_yet_another_simple_key_cveak: + content_view_environment_id: <%= ActiveRecord::FixtureSet.identify(:library_default_view_environment) %> + activation_key_id: <%= ActiveRecord::FixtureSet.identify(:and_yet_another_simple_key) %> + +purpose_attributes_key_cveak: + content_view_environment_id: <%= ActiveRecord::FixtureSet.identify(:library_default_view_environment) %> + activation_key_id: <%= ActiveRecord::FixtureSet.identify(:purpose_attributes_key) %> + +library_dev_staging_view_key_cveak: + content_view_environment_id: <%= ActiveRecord::FixtureSet.identify(:library_dev_staging_view_dev) %> + activation_key_id: <%= ActiveRecord::FixtureSet.identify(:library_dev_staging_view_key) %> + + + diff --git a/test/models/activation_key_test.rb b/test/models/activation_key_test.rb index e5e19af5293..819a6019181 100644 --- a/test/models/activation_key_test.rb +++ b/test/models/activation_key_test.rb @@ -5,6 +5,8 @@ class ActivationKeyTest < ActiveSupport::TestCase def setup @dev_key = katello_activation_keys(:dev_key) @dev_staging_view_key = katello_activation_keys(:library_dev_staging_view_key) + @dev_staging_cve = katello_content_view_environments(:library_dev_staging_view_dev) + @dev_view = katello_content_views(:library_dev_view) @lib_view = katello_content_views(:library_view) @pool_one = katello_pools(:pool_one) @@ -16,18 +18,17 @@ def setup should allow_values(*valid_name_list).for(:description) should_not allow_values(-1, 0, 'foo').for(:max_hosts) - test "can have content view" do + test "can have content view environment" do @dev_key = katello_activation_keys(:dev_key) - @dev_key.content_view = @dev_view + @dev_key.content_view_environments = [@dev_staging_cve] assert @dev_key.save! - assert_not_nil @dev_key.content_view - assert_includes @dev_view.activation_keys, @dev_key + assert_not_nil @dev_key.single_content_view + assert_includes @dev_staging_cve.content_view.activation_keys, @dev_key end - test "does not require a content view" do - assert_nil @dev_key.content_view - assert @dev_key.save! - assert_nil @dev_key.content_view + test "does not require a content view environment" do + assert @dev_key.update(content_view_environments: []) + assert_nil @dev_key.single_content_view end test "content view must be in environment" do @@ -55,15 +56,12 @@ def setup end test "key can be copied" do - @dev_key.content_view = katello_content_views(:acme_default) - @dev_key.environment = katello_environments(:library) @dev_key.max_hosts = 200 new_key = @dev_key.copy("new key name") - assert_equal new_key.name, "new key name" assert_equal new_key.description, @dev_key.description assert_equal new_key.host_collections, @dev_key.host_collections - assert_equal new_key.content_view, @dev_key.content_view + assert_equal new_key.content_view_environments, @dev_key.content_view_environments assert_equal new_key.organization, @dev_key.organization assert_equal new_key.max_hosts, @dev_key.max_hosts end diff --git a/test/models/authorization/activation_key_authorization_test.rb b/test/models/authorization/activation_key_authorization_test.rb index 03fd3f33f12..06d5b1bb571 100644 --- a/test/models/authorization/activation_key_authorization_test.rb +++ b/test/models/authorization/activation_key_authorization_test.rb @@ -79,7 +79,7 @@ def setup def test_all_editable? ak = ActivationKey.find(katello_activation_keys(:library_dev_staging_view_key).id) - keys = ActivationKey.where(:content_view_id => ak.content_view_id, :environment_id => ak.environment) + keys = ActivationKey.in_content_views_and_environments(content_views: ak.single_content_view, lifecycle_environments: ak.single_lifecycle_environment) clause = keys.map { |key| "name=\"#{key.name}\"" }.join(" or ") diff --git a/test/models/content_view_environment_activation_key_test.rb b/test/models/content_view_environment_activation_key_test.rb new file mode 100644 index 00000000000..de98a7c3b42 --- /dev/null +++ b/test/models/content_view_environment_activation_key_test.rb @@ -0,0 +1,29 @@ +require 'katello_test_helper' + +module Katello + class ContentViewEnvironmentActivationKeyTest < ActiveSupport::TestCase + def setup + User.current = User.find(users(:admin).id) + @activation_key = katello_activation_keys(:simple_key) + end + + def teardown + Setting['allow_multiple_content_views'] = false + end + + def test_reprioritize_for_activation_key + Setting['allow_multiple_content_views'] = true + @activation_key.content_view_environments = [ + katello_content_view_environments(:library_dev_view_dev), + katello_content_view_environments(:library_dev_staging_view_dev)] + + cve1 = @activation_key.content_view_environments.first + cve2 = @activation_key.content_view_environments.last + new_cves = [cve2, cve1] + ContentViewEnvironmentActivationKey.reprioritize_for_activation_key(@activation_key, new_cves) + @activation_key.content_view_environments.reload + assert_equal 1, cve1.priority(@activation_key) + assert_equal 0, cve2.priority(@activation_key) + end + end +end diff --git a/test/services/product_content_finder_test.rb b/test/services/product_content_finder_test.rb index 708d70d19d4..b24a6226a9f 100644 --- a/test/services/product_content_finder_test.rb +++ b/test/services/product_content_finder_test.rb @@ -22,7 +22,7 @@ def setup class ProductContentFinderActivationKeyTest < ProductContentFinderTestBase def setup super - @key = ActivationKey.new(:organization => @product1.organization) + @key = katello_activation_keys(:simple_key) end def test_all @@ -44,8 +44,10 @@ def test_match_subs end def test_match_environments - @key.environment = @repo2_cv.environment - @key.content_view = @repo2_cv.content_view + cves = ::Katello::ContentViewEnvironment.where(environment_id: @repo2_cv.environment, + content_view_id: @repo2_cv.content_view) + + @key.update!(content_view_environments: cves) Katello::Repository.where(:root => Katello::RootRepository.where(:content_id => @repo1.content_id), :content_view_version_id => @key.content_view.version(@key.environment)).destroy_all