From dd43f60a03c2b5f9157f278eaec63ecd7bbfba57 Mon Sep 17 00:00:00 2001 From: Ian Ballou Date: Fri, 6 Dec 2024 22:57:51 +0000 Subject: [PATCH] Fixes #38072 - add host bootc_images endpoint --- .../api/v2/host_bootc_images_controller.rb | 32 ++++++++++++ config/routes/overrides.rb | 1 + .../v2/host_bootc_images_controller_test.rb | 52 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 app/controllers/katello/api/v2/host_bootc_images_controller.rb create mode 100644 test/controllers/api/v2/host_bootc_images_controller_test.rb diff --git a/app/controllers/katello/api/v2/host_bootc_images_controller.rb b/app/controllers/katello/api/v2/host_bootc_images_controller.rb new file mode 100644 index 00000000000..ee8f492fa90 --- /dev/null +++ b/app/controllers/katello/api/v2/host_bootc_images_controller.rb @@ -0,0 +1,32 @@ +module Katello + class Api::V2::HostBootcImagesController < Api::V2::ApiController + 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 :page, :number, :desc => N_("Page number, starting at 1") + param :per_page, :number, :desc => N_("Number of results per page to return") + def bootc_images + bootc_image_map = bootc_host_image_map + page = params[:page] || 1 + per_page = params[:per_page] || Setting[:entries_per_page] + paged_results = bootc_image_map.to_a.paginate(page: page, per_page: per_page) + render json: { total: bootc_image_map.size, bootc_images: paged_results } + end + + private + + def bootc_host_image_map + aggregate_bootc_data = ::Katello::Host::ContentFacet.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(:bootc_booted_image) + 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 diff --git a/config/routes/overrides.rb b/config/routes/overrides.rb index 142312d6e8d..32bcf9d5fdb 100644 --- a/config/routes/overrides.rb +++ b/config/routes/overrides.rb @@ -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 diff --git a/test/controllers/api/v2/host_bootc_images_controller_test.rb b/test/controllers/api/v2/host_bootc_images_controller_test.rb new file mode 100644 index 00000000000..ab7b6a4d833 --- /dev/null +++ b/test/controllers/api/v2/host_bootc_images_controller_test.rb @@ -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