diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 58866fec..0f6bcf76 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -3,10 +3,15 @@ == 0.5.0 - unreleased :224: https://github.com/stackabletech/agent/pull/224[#224] +:229: https://github.com/stackabletech/agent/pull/229[#229] === Added * `hostIP` and `podIP` added to the pod status ({224}). +=== Fixed +* Invalid or unreachable repositories are skipped when searching for + packages ({229}). + == 0.4.0 - 2021-06-23 :188: https://github.com/stackabletech/agent/pull/188[#188] diff --git a/src/provider/repository/mod.rs b/src/provider/repository/mod.rs index 58ce6b53..e2e6062b 100644 --- a/src/provider/repository/mod.rs +++ b/src/provider/repository/mod.rs @@ -1,52 +1,96 @@ -use std::convert::TryFrom; +//! Functions to deal with Stackable repositories -use kube::api::ListParams; +use kube::api::{ListParams, ObjectList}; use kube::{Api, Client}; -use log::{debug, trace}; +use log::{debug, info, warn}; +use std::convert::TryFrom; use crate::provider::error::StackableError; -use crate::provider::repository::package::Package; -use crate::provider::repository::repository_spec::Repository; -use crate::provider::repository::stackablerepository::StackableRepoProvider; +use package::Package; +use repository_spec::Repository; +use stackablerepository::StackableRepoProvider; pub mod package; pub mod repository_spec; pub mod stackablerepository; +/// Searches for the given package in all registered repositories. +/// +/// The available repositories are retrieved from the API server and if +/// the given package is provided by one of them then +/// `Ok(Some(repository))` else `Ok(None)` is returned. +/// +/// If the repositories cannot be retrieved then `Err(error)` is +/// returned. +/// +/// The repositories are sorted by their name to provide a deterministic +/// behavior especially for tests. pub async fn find_repository( client: Client, package: &Package, - repository_reference: Option, ) -> Result, StackableError> { - let repositories: Api = Api::namespaced(client.clone(), "default"); - if let Some(repository_name) = repository_reference { - // A repository name was provided, just check that exact repository for the package - let repo = repositories.get(&repository_name).await?; - let mut repo = StackableRepoProvider::try_from(&repo)?; - if repo.provides_package(package.clone()).await? { - return Ok(Some(repo)); - } - return Ok(None); + let repositories = retrieve_repositories(client).await?; + + let mut repo_providers = repositories + .iter() + .filter_map(convert_to_repo_provider) + .collect::>(); + + repo_providers.sort_unstable_by_key(|repo_provider| repo_provider.name.to_owned()); + + let maybe_repo_provider = choose_repo_provider(&mut repo_providers, package).await; + + if let Some(repo_provider) = &maybe_repo_provider { + debug!( + "Package [{}] found in repository [{}]", + &package, &repo_provider + ); } else { - // No repository name was provided, retrieve all repositories from the orchestrator/apiserver - // and check which one provides the package - let list_params = ListParams::default(); - let repos = repositories.list(&list_params).await?; - for repository in repos.iter() { - debug!("got repo definition: [{:?}]", repository); - // Convert repository to object implementing our trait - let mut repo = StackableRepoProvider::try_from(repository)?; - trace!("converted to stackable repo: {:?}", repository); - if repo.provides_package(package.clone()).await? { - debug!("Found package [{}] in repository [{}]", &package, repo); - return Ok(Some(repo)); - } else { - debug!( - "Package [{}] not provided by repository [{}]", - &package, repo - ); - } + let repository_names = repo_providers + .iter() + .map(|repo_provider| repo_provider.name.as_str()) + .collect::>(); + info!( + "Package [{}] not found in the following repositories: {:?}", + package, repository_names + ); + } + + Ok(maybe_repo_provider) +} + +/// Retrieves all Stackable repositories in the default namespace from +/// the API server. +async fn retrieve_repositories(client: Client) -> Result, StackableError> { + let api: Api = Api::namespaced(client, "default"); + let repositories = api.list(&ListParams::default()).await?; + Ok(repositories) +} + +/// Converts the given Stackable repository into a repository provider. +/// +/// If this fails then a warning is emitted and `None` is returned. +fn convert_to_repo_provider(repository: &Repository) -> Option { + let result = StackableRepoProvider::try_from(repository); + + if let Err(error) = &result { + warn!("Invalid repository definition: {}", error); + } + + result.ok() +} + +/// Retrieves the provided packages for the given repository providers +/// and returns the first provider which provides the given package or +/// `None` if none provides it. +async fn choose_repo_provider( + repo_providers: &mut [StackableRepoProvider], + package: &Package, +) -> Option { + for repo_provider in repo_providers { + if let Ok(true) = repo_provider.provides_package(package.to_owned()).await { + return Some(repo_provider.to_owned()); } } - Ok(None) + None } diff --git a/src/provider/states/pod/downloading.rs b/src/provider/states/pod/downloading.rs index 23677aa8..5669e051 100644 --- a/src/provider/states/pod/downloading.rs +++ b/src/provider/states/pod/downloading.rs @@ -60,7 +60,7 @@ impl State for Downloading { }, ); } - let repo = find_repository(client, &package, None).await; + let repo = find_repository(client, &package).await; return match repo { Ok(Some(mut repo)) => { // We found a repository providing the package, proceed with download