From b14382d34ae8a8ab514897f2ae93d9c77546b202 Mon Sep 17 00:00:00 2001 From: Samir Jha Date: Mon, 8 Sep 2025 21:57:03 +0000 Subject: [PATCH] Fixes #38728 - Smart proxy sync can fail when host has no bound container repos --- .../container_gateway_main.rb | 37 +++++--- test/container_gateway_backend_test.rb | 94 +++++++++++++++++++ 2 files changed, 118 insertions(+), 13 deletions(-) diff --git a/lib/smart_proxy_container_gateway/container_gateway_main.rb b/lib/smart_proxy_container_gateway/container_gateway_main.rb index beb8a1b..6806a1e 100644 --- a/lib/smart_proxy_container_gateway/container_gateway_main.rb +++ b/lib/smart_proxy_container_gateway/container_gateway_main.rb @@ -195,29 +195,40 @@ def update_host_repo_mapping(host_repo_maps) # Insert all in a single transaction database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do hosts_repositories.delete - hosts_repositories.import(%i[repository_id host_id], entries) + hosts_repositories.import(%i[repository_id host_id], entries) unless entries.nil? || entries.empty? end end def build_host_repository_mapping(host_repo_maps) + return [] if host_repo_maps['hosts'].nil? + hosts = database.connection[:hosts] repositories = database.connection[:repositories] + entries = host_repo_maps['hosts'].flat_map do |host_map| host_map.filter_map do |host_uuid, repos| - host = hosts[{ uuid: host_uuid }] - next unless host - - repo_names = repos - .select { |repo| repo['auth_required'].to_s.downcase == "true" } - .map { |repo| repo['repository'] } - - repositories - .where(name: repo_names, auth_required: true) - .select(:id) - .map { |repo| [repo[:id], host[:id]] } + build_host_entries(hosts, repositories, host_uuid, repos) end end - entries.flatten!(1) + entries&.flatten(1)&.compact + end + + def build_host_entries(hosts, repositories, host_uuid, repos) + host = hosts[{ uuid: host_uuid }] + return unless host + return if repos.nil? || repos.empty? + + repo_names = extract_auth_required_repo_names(repos) + repositories + .where(name: repo_names, auth_required: true) + .select(:id) + .map { |repo| [repo[:id], host[:id]] } + end + + def extract_auth_required_repo_names(repos) + repos + .select { |repo| repo['auth_required'].to_s.downcase == "true" } + .map { |repo| repo['repository'] } end def update_host_repositories(uuid, repositories) diff --git a/test/container_gateway_backend_test.rb b/test/container_gateway_backend_test.rb index d0b6d59..619b4d3 100644 --- a/test/container_gateway_backend_test.rb +++ b/test/container_gateway_backend_test.rb @@ -233,5 +233,99 @@ def clears_existing_host_repo_mapping_before_update assert_equal [[updated_repo[:id], host[:id]]], @database.connection[:hosts_repositories].select_map(%i[repository_id host_id]) end + + def test_build_host_repository_mapping_with_nil_hosts + host_repo_maps = { 'hosts' => nil } + result = @container_gateway_main.build_host_repository_mapping(host_repo_maps) + assert_empty result + end + + def test_build_host_repository_mapping_with_empty_hosts + host_repo_maps = { 'hosts' => [] } + result = @container_gateway_main.build_host_repository_mapping(host_repo_maps) + assert_empty result + end + + def test_build_host_repository_mapping_with_nonexistent_host + @container_gateway_main.update_repository_list([{ 'repository' => 'test_repo1', 'auth_required' => true }]) + + host_repo_maps = { + 'hosts' => [ + { 'nonexistent-uuid' => [{ 'repository' => 'test_repo1', 'auth_required' => true }] } + ] + } + + result = @container_gateway_main.build_host_repository_mapping(host_repo_maps) + assert_empty result + end + + def test_build_host_repository_mapping_with_nil_repos + @database.connection[:hosts].insert(uuid: 'host-uuid-1') + + host_repo_maps = { + 'hosts' => [ + { 'host-uuid-1' => nil } + ] + } + + result = @container_gateway_main.build_host_repository_mapping(host_repo_maps) + assert_empty result + end + + def test_build_host_repository_mapping_with_empty_repos + @database.connection[:hosts].insert(uuid: 'host-uuid-1') + + host_repo_maps = { + 'hosts' => [ + { 'host-uuid-1' => [] } + ] + } + + result = @container_gateway_main.build_host_repository_mapping(host_repo_maps) + assert_empty result + end + + def test_build_host_repository_mapping_filters_non_auth_required_repos + repo_list = [{ 'repository' => 'public_repo', 'auth_required' => false }, + { 'repository' => 'private_repo', 'auth_required' => true }] + @container_gateway_main.update_repository_list(repo_list) + @database.connection[:hosts].insert(uuid: 'host-uuid-1') + + host_repo_maps = { + 'hosts' => [ + { 'host-uuid-1' => [ + { 'repository' => 'public_repo', 'auth_required' => false }, + { 'repository' => 'private_repo', 'auth_required' => true } + ] } + ] + } + + result = @container_gateway_main.build_host_repository_mapping(host_repo_maps) + + host = @database.connection[:hosts].first(uuid: 'host-uuid-1') + private_repo = @database.connection[:repositories].first(name: 'private_repo') + + assert_equal [[private_repo[:id], host[:id]]], result + end + + def test_build_host_repository_mapping_handles_mixed_scenarios + @container_gateway_main.update_repository_list([{ 'repository' => 'test_repo1', 'auth_required' => true }]) + @database.connection[:hosts].insert(uuid: 'valid-host') + + host_repo_maps = { + 'hosts' => [ + { 'valid-host' => [{ 'repository' => 'test_repo1', 'auth_required' => true }] }, + { 'invalid-host' => [{ 'repository' => 'test_repo1', 'auth_required' => true }] }, + { 'empty-repos-host' => [] } + ] + } + + result = @container_gateway_main.build_host_repository_mapping(host_repo_maps) + + host = @database.connection[:hosts].first(uuid: 'valid-host') + repo = @database.connection[:repositories].first(name: 'test_repo1') + + assert_equal [[repo[:id], host[:id]]], result + end end # rubocop:enable Metrics/AbcSize