module Katello class Api::V2::ContentViewsController < Api::V2::ApiController include Concerns::Authorization::Api::V2::ContentViewsController include Katello::Concerns::FilteredAutoCompleteSearch include Katello::Concerns::Api::V2::BulkExtensions before_action :find_authorized_katello_resource, :except => [:index, :create, :copy, :auto_complete_search] before_action :ensure_non_generated, only: [:publish] before_action :ensure_non_default, :except => [:index, :create, :copy, :auto_complete_search] before_action :find_organization, :only => [:create] before_action :find_optional_organization, :only => [:index, :auto_complete_search] before_action :find_environment, :only => [:index, :remove_from_environment] wrap_parameters :include => (ContentView.attribute_names + %w(repository_ids component_ids)) resource_description do api_version "v2" end def_param_group :content_view do param :description, String, :desc => N_("Description for the content view") param :repository_ids, Array, :desc => N_("List of repository ids") param :component_ids, Array, :desc => N_("List of component content view version ids for composite views") param :auto_publish, :bool, :desc => N_("Enable/Disable auto publish of composite view") param :solve_dependencies, :bool, :desc => N_("Solve RPM dependencies by default on Content View publish, defaults to false") param :import_only, :bool, :desc => N_("Designate this Content View for importing from upstream servers only. Defaults to false") end def_param_group :bulk_content_view_version_ids do param :included, Hash, :desc => N_("Versions to exclusively include in the action"), :required => true, :action_aware => true do param :search, String, :required => false, :desc => N_("Search string for versions to perform an action on") param :ids, Array, :required => false, :desc => N_("List of versions to perform an action on") end param :excluded, Hash, :desc => N_("Versions to explicitly exclude in the action."\ " All other versions will be included in the action,"\ " unless an included parameter is passed as well."), :required => true, :action_aware => true do param :ids, Array, :required => false, :desc => N_("List of versions to exclude and not run an action on") end end def filtered_associations { :component_ids => Katello::ContentViewVersion, :repository_ids => Katello::Repository } end api :GET, "/organizations/:organization_id/content_views", N_("List content views") api :GET, "/content_views", N_("List content views") param :organization_id, :number, :desc => N_("organization identifier") param :environment_id, :number, :desc => N_("environment identifier") param :nondefault, :bool, :desc => N_("Filter out default content views") param :noncomposite, :bool, :desc => N_("Filter out composite content views") param :composite, :bool, :desc => N_("Filter only composite content views") param :without, Array, :desc => N_("Do not include this array of content views") param :name, String, :desc => N_("Name of the content view"), :required => false param :label, String, :desc => N_("Label of the content view"), :required => false param :include_generated, :bool, :desc => N_("Include content views generated by imports/exports. Defaults to false") param_group :search, Api::V2::ApiController add_scoped_search_description_for(ContentView) def index content_view_includes = [:activation_keys, :content_view_versions, :content_view_components, :environments, :organization, :repositories] respond(:collection => scoped_search(index_relation.distinct, :name, :asc, :includes => content_view_includes)) end def index_relation content_views = ContentView.readable content_views = content_views.ignore_generated unless Foreman::Cast.to_bool(params[:include_generated]) content_views = content_views.where(:organization_id => @organization.id) if @organization content_views = content_views.in_environment(@environment) if @environment content_views = ::Foreman::Cast.to_bool(params[:nondefault]) ? content_views.non_default : content_views.default if params[:nondefault] content_views = ::Foreman::Cast.to_bool(params[:noncomposite]) ? content_views.non_composite : content_views.composite if params[:noncomposite] content_views = ::Foreman::Cast.to_bool(params[:composite]) ? content_views.composite : content_views.non_composite if params[:composite] content_views = content_views.where(:name => params[:name]) if params[:name] content_views = content_views.where(:label => params[:label]) if params[:label] content_views = content_views.where("#{ContentView.table_name}.id NOT IN (?)", params[:without]) if params[:without] content_views end api :POST, "/organizations/:organization_id/content_views", N_("Create a content view") api :POST, "/content_views", N_("Create a content view") param :organization_id, :number, :desc => N_("Organization identifier"), :required => true param :name, String, :desc => N_("Name of the content view"), :required => true param :label, String, :desc => N_("Content view label") param :composite, :bool, :desc => N_("Composite content view") param_group :content_view def create @content_view = ContentView.create!(view_params) do |view| view.organization = @organization view.label ||= labelize_params(params[:content_view]) end respond :resource => @content_view end api :PUT, "/content_views/:id", N_("Update a content view") param :id, :number, :desc => N_("Content view identifier"), :required => true param :name, String, :desc => N_("New name for the content view") param_group :content_view def update sync_task(::Actions::Katello::ContentView::Update, @content_view, view_params) respond :resource => @content_view.reload end api :POST, "/content_views/:id/publish", N_("Publish a content view") param :id, :number, :desc => N_("Content view identifier"), :required => true param :description, String, :desc => N_("Description for the new published content view version") param :major, :number, :desc => N_("Override the major version number"), :required => false param :minor, :number, :desc => N_("Override the minor version number"), :required => false param :environment_ids, Array, :desc => N_("Identifiers for Lifecycle Environment"), :required => false param :publish_only_if_needed, :bool, :desc => N_("Check audited changes and proceed only if content or filters have changed since last publish"), :required => false param :is_force_promote, :bool, :desc => N_("Force content view promotion and bypass lifecycle environment restriction"), :required => false param :repos_units, Array, :desc => N_("Specify the list of units in each repo"), :required => false do param :label, String, :desc => N_("repo label"), :required => true param :rpm_filenames, Array, of: String, :desc => N_("list of rpm filename strings to include in published version"), :required => true end def publish validate_publish_params! task = async_task(::Actions::Katello::ContentView::Publish, @content_view, params[:description], :environment_ids => params[:environment_ids], :is_force_promote => params[:is_force_promote], :major => params[:major], :minor => params[:minor], :repos_units => params[:repos_units]) respond_for_async :resource => task end api :GET, "/content_views/:id", N_("Show a content view") param :id, :number, :desc => N_("content view numeric identifier"), :required => true def show respond :resource => @content_view end api :DELETE, "/content_views/:id/environments/:environment_id", N_("Remove a content view from an environment") param :id, :number, :desc => N_("content view numeric identifier"), :required => true param :environment_id, :number, :desc => N_("environment numeric identifier"), :required => true def remove_from_environment unless @content_view.environments.include?(@environment) fail HttpErrors::BadRequest, _("Content view '%{view}' is not in lifecycle environment '%{env}'.") % {view: @content_view.name, env: @environment.name} end task = async_task(::Actions::Katello::ContentView::RemoveFromEnvironment, @content_view, @environment) respond_for_async :resource => task end api :PUT, "/content_views/:id/remove", N_("Remove versions and/or environments from a content view and reassign systems and keys") param :id, :number, :desc => N_("content view numeric identifier"), :required => true param :environment_ids, Array, of: :number, :desc => N_("environment numeric identifiers to be removed") param :content_view_version_ids, Array, of: :number, :desc => N_("content view version identifiers to be deleted") param :system_content_view_id, :number, :desc => N_("content view to reassign orphaned systems to") param :system_environment_id, :number, :desc => N_("environment to reassign orphaned systems to") param :key_content_view_id, :number, :desc => N_("content view to reassign orphaned activation keys to") param :key_environment_id, :number, :desc => N_("environment to reassign orphaned activation keys to") param :destroy_content_view, :boolean, :desc => N_("delete the content view with all the versions and environments") def remove if params[:destroy_content_view] cv_envs = @content_view.content_view_environments versions = @content_view.versions else cv_envs = ContentViewEnvironment.where(:environment_id => params[:environment_ids], :content_view_id => params[:id] ) versions = @content_view.versions.where(:id => params[:content_view_version_ids]) end if !params[:destroy_content_view] && cv_envs.empty? && versions.empty? fail _("There either were no environments nor versions specified or there were invalid environments/versions specified. "\ "Please check environment_ids and content_view_version_ids parameters.") end options = params.slice(:system_content_view_id, :system_environment_id, :key_content_view_id, :key_environment_id, :destroy_content_view ).reject { |_k, v| v.nil? }.to_unsafe_h options[:content_view_versions] = versions options[:content_view_environments] = cv_envs task = async_task(::Actions::Katello::ContentView::Remove, @content_view, options) respond_for_async :resource => task end api :PUT, "/content_views/:id/bulk_delete_versions", N_("Bulk remove versions from a content view and reassign systems and keys") param_group :bulk_content_view_version_ids param :id, :number, :desc => N_("content view numeric identifier"), :required => true param :system_content_view_id, :number, :desc => N_("content view to reassign orphaned systems to") param :system_environment_id, :number, :desc => N_("environment to reassign orphaned systems to") param :key_content_view_id, :number, :desc => N_("content view to reassign orphaned activation keys to") param :key_environment_id, :number, :desc => N_("environment to reassign orphaned activation keys to") def bulk_delete_versions params[:bulk_content_view_version_ids] ||= {} versions = find_bulk_items(bulk_params: params[:bulk_content_view_version_ids], model_scope: ::Katello::ContentViewVersion.where(content_view_id: @content_view.id), key: :id) cv_envs = ContentViewEnvironment.where(:content_view_version_id => versions.pluck(:id), :content_view_id => @content_view.id ) if !params[:destroy_content_view] && cv_envs.empty? && versions.empty? fail _("There either were no environments nor versions specified or there were invalid environments/versions specified. "\ "Please check environment_ids and content_view_version_ids parameters.") end options = params.slice(:system_content_view_id, :system_environment_id, :key_content_view_id, :key_environment_id ).reject { |_k, v| v.nil? }.to_unsafe_h options[:content_view_versions] = versions options[:content_view_environments] = cv_envs task = async_task(::Actions::Katello::ContentView::Remove, @content_view, options) respond_for_async :resource => task end api :PUT, "/content_views/:id/remove_filters", N_("Delete multiple filters from a content view") param :id, :number, :desc => N_("content view numeric identifier"), :required => true param :filter_ids, Array, of: :number, :desc => N_("filter identifiers"), :required => true def remove_filters Katello::ContentViewFilter.where(id: params[:filter_ids]).try(:destroy_all) respond_for_show(:resource => @content_view) end api :DELETE, "/content_views/:id", N_("Delete a content view") param :id, :number, :desc => N_("content view numeric identifier"), :required => true def destroy task = async_task(::Actions::Katello::ContentView::Destroy, @content_view) respond_for_async :resource => task end api :POST, "/content_views/:id/copy", N_("Make copy of a content view") param :id, :number, :desc => N_("Content view numeric identifier"), :required => true param :name, String, :required => true, :desc => N_("New content view name") def copy @content_view = Katello::ContentView.readable.find_by(:id => params[:id]) throw_resource_not_found(name: 'content_view', id: params[:id]) if @content_view.blank? ensure_non_default new_content_view = @content_view.copy(params[:content_view][:name]) respond_for_create :resource => new_content_view end private def validate_publish_params! if params[:repos_units].present? && @content_view.composite? fail HttpErrors::BadRequest, _("Directly setting package lists on composite content views is not allowed. Please " \ "update the components, then re-publish the composite.") end if params[:major].present? && params[:minor].present? && ContentViewVersion.find_by(:content_view_id => params[:id], :major => params[:major], :minor => params[:minor]).present? fail HttpErrors::BadRequest, _("A CV version already exists with the same major and minor version (%{major}.%{minor})") % {:major => params[:major], :minor => params[:minor]} end if params[:major].present? && params[:minor].nil? || params[:major].nil? && params[:minor].present? fail HttpErrors::BadRequest, _("Both major and minor parameters have to be used to override a CV version") end cv_needs_publish = @content_view.needs_publish? if (::Foreman::Cast.to_bool(params[:publish_only_if_needed]) && !cv_needs_publish.nil? && !cv_needs_publish) fail HttpErrors::BadRequest, _("Content view does not need a publish since there are no audited changes since the last publish." \ " Pass check_needs_publish parameter as false if you don't want to check if content view needs a publish.") end end def ensure_non_default if @content_view.default? && !%w(show history).include?(params[:action]) fail HttpErrors::BadRequest, _("The default content view cannot be edited, published, or deleted.") end end def ensure_non_generated if @content_view.import_only? fail HttpErrors::BadRequest, _("Import only Content Views cannot be directly publsihed. Content can only be updated by importing into the view.") end if @content_view.generated? fail HttpErrors::BadRequest, _("Generated content views cannot be directly published. They can updated only via export.") end end def view_params attrs = [:name, :description, :auto_publish, :solve_dependencies, :import_only, :default, :created_at, :updated_at, :next_version, {:component_ids => []}] attrs.push(:label, :composite) if action_name == "create" if (!@content_view || !@content_view.composite?) attrs.push({:repository_ids => []}, :repository_ids) end params.require(:content_view).permit(*attrs).to_h end def find_environment return if !params.key?(:environment_id) && params[:action] == "index" @environment = KTEnvironment.readable.find(params[:environment_id]) end def add_use_latest_records(module_records, selected_latest_versions) module_records.group_by(&:author).each_pair do |_author, records| top_rec = records[0] latest = top_rec.dup latest.version = _("Always Use Latest (currently %{version})") % { version: latest.version } latest.pulp_id = nil module_records.delete(top_rec) if selected_latest_versions.include?(top_rec.pulp_id) module_records.push(latest) end module_records end end end