Skip to content

Commit

Permalink
Fixes #38072 - add host bootc_images endpoint (#11257)
Browse files Browse the repository at this point in the history
* Fixes #38072 - add host bootc_images endpoint

* Refs #38072 - bootc api pagination data + UI-friendly data re-org

* Refs #38072 - add host searching to bootc images api

* Refs #38072 - select default pagination properly

* Refs #38072 - add sorting support to bootc_image api
  • Loading branch information
ianballou authored Jan 7, 2025
1 parent fb6cca7 commit ec7ef5c
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 0 deletions.
61 changes: 61 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,61 @@
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
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
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 => [:auto_complete_search] 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
2 changes: 2 additions & 0 deletions lib/katello/permissions/host_permissions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@

Foreman::AccessControl.permission(:view_hosts).actions.concat [
'hosts/content_hosts',
'katello/api/v2/host_bootc_images/bootc_images',
'katello/api/v2/host_bootc_images/auto_complete_search',
'katello/api/v2/host_autocomplete/auto_complete_search',
'katello/api/v2/host_errata/index',
'katello/api/v2/host_errata/show',
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)['results']
assert_includes results, {"bootc_booted_image" => "image1", "digests" => [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace23dd07ff3116168c2b832e16bba8234525724a3", "host_count" => 2}]}
assert_includes results, {"bootc_booted_image" => "image2", "digests" => [{"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 [{"bootc_booted_image" => "image1", "digests" => [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace23dd07ff3116168c2b832e16bba8234525724a3", "host_count" => 1}]}], JSON.parse(page1)['results']
assert_equal [{"bootc_booted_image" => "image2", "digests" => [{"bootc_booted_digest" => "sha256:dcfb2965cda67bc3731408aae23dd07ff3116168c2b832e16bba8234525724a5", "host_count" => 1}]}], JSON.parse(page2)['results']
assert_equal [{"bootc_booted_image" => "image3", "digests" => [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace93dd07ff3116168c2b832e16bba8234525724c9", "host_count" => 1}]}], JSON.parse(page3)['results']
assert_empty JSON.parse(page4)['results']
end
end
end

0 comments on commit ec7ef5c

Please sign in to comment.