Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #38107 - Booted container images page #11269

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
62 changes: 62 additions & 0 deletions app/controllers/katello/api/v2/host_bootc_images_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module Katello
class Api::V2::HostBootcImagesController < Api::V2::ApiController
include Katello::Concerns::FilteredAutoCompleteSearch

resource_description do
api_version 'v2'
api_base_url "/api"
end

api :GET, "/hosts/bootc_images", N_("List booted bootc container images for hosts")
param_group :search, Api::V2::ApiController
def bootc_images
params[:sort_by] ||= 'bootc_booted_image'
params[:sort_order] ||= 'asc'
if params[:order]
params[:order] = "#{params[:order].split(' ')[0]} #{sanitize_sort_order(params[:order].split(' ')[1])}"
else
params[:order] = "#{params[:sort_by]} #{sanitize_sort_order(params[:sort_order])}"
end
per_page = params[:per_page].present? ? params[:per_page].to_i : Setting[:entries_per_page]
page = params[:page].present? ? params[:page].to_i : 1

bootc_image_map = bootc_host_image_map
paged_images = bootc_image_map.to_a.paginate(page: page, per_page: per_page)
results = paged_images.collect { |image| { bootc_booted_image: image[0], digests: image[1] } }
render json: { total: bootc_image_map.size, page: page, per_page: per_page, subtotal: bootc_image_map.size, results: results}
end

private

def sanitize_sort_order(sort_order)
if sort_order.present? && ['asc', 'desc'].include?(sort_order.downcase)
sort_order.downcase
else
'asc'
end

Check failure on line 37 in app/controllers/katello/api/v2/host_bootc_images_controller.rb

View workflow job for this annotation

GitHub Actions / Rubocop / Rubocop

Layout/EmptyLinesAroundMethodBody: Extra empty line detected at method body end.
end

def index_relation
query = resource_class.authorized(:view_hosts).distinct
query.joins(:content_facet).where.not(bootc_booted_image: nil, bootc_booted_digest: nil)
query
end

def resource_class
::Host::Managed
end

def bootc_host_image_map
content_facets = ::Katello::Host::ContentFacet.where(host_id: ::Host::Managed.joins(:content_facet).search_for(params[:search]).pluck(:id))
aggregate_bootc_data = content_facets.where.not(bootc_booted_image: nil, bootc_booted_digest: nil).
select(:bootc_booted_image, :bootc_booted_digest, 'COUNT(hosts.id) as host_count').
joins(:host).group(:bootc_booted_image, :bootc_booted_digest).order(params[:order])
bootc_image_map = Hash.new { |h, k| h[k] = [] }
aggregate_bootc_data.each do |host_image|
bootc_image_map[host_image.bootc_booted_image] << { bootc_booted_digest: host_image.bootc_booted_digest, host_count: host_image.host_count.to_i }
end
bootc_image_map
end
end
end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
match '/alternate_content_sources' => 'react#index', :via => [:get]
match '/alternate_content_sources/*page' => 'react#index', :via => [:get]

match '/booted_container_images' => 'react#index', :via => [:get]

Katello::RepositoryTypeManager.generic_ui_content_types(false).each do |type|
get "/#{type.pluralize}", to: redirect("/content/#{type.pluralize}")
get "/#{type.pluralize}/:page", to: redirect("/content/#{type.pluralize}/%{page}")
Expand Down
4 changes: 4 additions & 0 deletions config/routes/api/v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class ActionDispatch::Routing::Mapper
end
end

api_resources :host_bootc_images, :only => [:bootc_images] do
get :auto_complete_search, :on => :collection
end

api_resources :capsules, :only => [:index, :show] do
member do
resource :content, :only => [], :controller => 'capsule_content' do
Expand Down
1 change: 1 addition & 0 deletions config/routes/overrides.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def matches?(request)

collection do
match '/auto_complete_search' => 'host_autocomplete#auto_complete_search', :via => :get
match '/bootc_images' => 'host_bootc_images#bootc_images', :via => :get
match '/bulk/add_host_collections' => 'hosts_bulk_actions#bulk_add_host_collections', :via => :put
match '/bulk/remove_host_collections' => 'hosts_bulk_actions#bulk_remove_host_collections', :via => :put
match '/bulk/remove_host_collections' => 'hosts_bulk_actions#bulk_remove_host_collections', :via => :put
Expand Down
9 changes: 9 additions & 0 deletions lib/katello/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@
:engine => Katello::Engine,
:turbolinks => false

menu :top_menu,
:booted_container_images,
:caption => N_('Booted container images'),
:url_hash => {:controller => 'katello/api/v2/host_bootc_images',
:action => 'bootc_images'},
:url => '/booted_container_images',
:engine => Katello::Engine,
:turbolinks => false

divider :top_menu, :caption => N_('Lifecycle'), :parent => :content_menu

menu :top_menu,
Expand Down
52 changes: 52 additions & 0 deletions test/controllers/api/v2/host_bootc_images_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# encoding: utf-8

require "katello_test_helper"

module Katello
class Api::V2::HostBootcImagesControllerTest < ActionController::TestCase
tests ::Katello::Api::V2::HostBootcImagesController

def setup
setup_controller_defaults_api
setup_foreman_routes
@host1 = FactoryBot.create(:host, :with_content, :with_subscription, :content_view => katello_content_views(:library_dev_view),
:lifecycle_environment => katello_environments(:library))
@host2 = FactoryBot.create(:host, :with_content, :with_subscription, :content_view => katello_content_views(:library_dev_view),
:lifecycle_environment => katello_environments(:library))
@host3 = FactoryBot.create(:host, :with_content, :with_subscription, :content_view => katello_content_views(:library_dev_view),
:lifecycle_environment => katello_environments(:library))
@host1.content_facet.update!(bootc_booted_image: 'image1')
@host1.content_facet.update!(bootc_booted_digest: 'sha256:dcfb2965cda67bd3731408ace23dd07ff3116168c2b832e16bba8234525724a3')
@host2.content_facet.update!(bootc_booted_image: 'image1')
@host2.content_facet.update!(bootc_booted_digest: 'sha256:dcfb2965cda67bd3731408ace23dd07ff3116168c2b832e16bba8234525724a3')
@host3.content_facet.update!(bootc_booted_image: 'image2')
@host3.content_facet.update!(bootc_booted_digest: 'sha256:dcfb2965cda67bc3731408aae23dd07ff3116168c2b832e16bba8234525724a5')
end

def test_bootc_images_counts_properly_no_paging
get :bootc_images
assert_response :success
results = JSON.parse(@response.body)['bootc_images']
assert_includes results, ["image1", [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace23dd07ff3116168c2b832e16bba8234525724a3", "host_count" => 2}]]
assert_includes results, ["image2", [{"bootc_booted_digest" => "sha256:dcfb2965cda67bc3731408aae23dd07ff3116168c2b832e16bba8234525724a5", "host_count" => 1}]]
end

def test_bootc_images_pages
@host2.content_facet.update!(bootc_booted_image: 'image3')
@host2.content_facet.update!(bootc_booted_digest: 'sha256:dcfb2965cda67bd3731408ace93dd07ff3116168c2b832e16bba8234525724c9')
get :bootc_images, params: { page: 1, per_page: 1 }
page1 = @response.body
get :bootc_images, params: { page: 2, per_page: 1 }
page2 = @response.body
get :bootc_images, params: { page: 3, per_page: 1 }
page3 = @response.body
get :bootc_images, params: { page: 4, per_page: 1 }
page4 = @response.body

assert_equal [["image1", [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace23dd07ff3116168c2b832e16bba8234525724a3", "host_count" => 1}]]], JSON.parse(page1)['bootc_images']
assert_equal [["image2", [{"bootc_booted_digest" => "sha256:dcfb2965cda67bc3731408aae23dd07ff3116168c2b832e16bba8234525724a5", "host_count" => 1}]]], JSON.parse(page2)['bootc_images']
assert_equal [["image3", [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace93dd07ff3116168c2b832e16bba8234525724c9", "host_count" => 1}]]], JSON.parse(page3)['bootc_images']
assert_empty JSON.parse(page4)['bootc_images']
end
end
end
5 changes: 5 additions & 0 deletions webpack/containers/Application/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import ContentDetails from '../../scenes/Content/Details';
import withHeader from './withHeaders';
import ChangeContentSource from '../../scenes/Hosts/ChangeContentSource';
import AlternateContentSource from '../../scenes/AlternateContentSources';
import BootedContainerImages from '../../scenes/BootedContainerImages';

// eslint-disable-next-line import/prefer-default-export
export const links = [
Expand Down Expand Up @@ -85,4 +86,8 @@ export const links = [
component: WithOrganization(withHeader(AlternateContentSource, { title: __('Alternate Content Sources') })),
exact: false,
},
{
path: 'booted_container_images',
component: WithOrganization(withHeader(BootedContainerImages, { title: __('Booted container images') })),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { translate as __ } from 'foremanReact/common/I18n';

Check failure on line 1 in webpack/scenes/BootedContainerImages/BootedContainerImagesConstants.js

View workflow job for this annotation

GitHub Actions / react-tests / Foreman develop Ruby 2.7 and Node 14

'__' is defined but never used

Check failure on line 1 in webpack/scenes/BootedContainerImages/BootedContainerImagesConstants.js

View workflow job for this annotation

GitHub Actions / react-tests / Foreman develop Ruby 2.7 and Node 18

'__' is defined but never used
import { foremanApi } from '../../services/api';

const BOOTED_CONTAINER_IMAGES_KEY = 'BOOTED_CONTAINER_IMAGES';
export const BOOTED_CONTAINER_IMAGES_API_PATH = foremanApi.getApiUrl('/hosts/bootc_images');
export default BOOTED_CONTAINER_IMAGES_KEY;
Loading
Loading