blob: f271a33120a6daf48069d47b494d2e3d840847c4 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
failure::Fail,
fidl_fuchsia_pkg_ext::{RepositoryConfig, RepositoryConfigs},
fuchsia_syslog::fx_log_err,
fuchsia_uri::pkg_uri::RepoUri,
std::collections::btree_set,
std::collections::hash_map::Entry,
std::collections::{BTreeSet, HashMap},
std::fmt,
std::fs,
std::io,
std::path::{Path, PathBuf},
};
/// [RepositoryManager] controls access to all the repository configs used by the package resolver.
#[derive(Debug, PartialEq, Eq)]
pub struct RepositoryManager {
dynamic_configs_path: PathBuf,
static_configs: HashMap<RepoUri, RepositoryConfig>,
dynamic_configs: HashMap<RepoUri, RepositoryConfig>,
}
impl RepositoryManager {
/// Returns a reference to the [RepositoryConfig] config identified by the config `repo_url`,
/// or `None` if it does not exist.
pub fn get(&self, repo_url: &RepoUri) -> Option<&RepositoryConfig> {
self.dynamic_configs.get(repo_url).or_else(|| self.static_configs.get(repo_url))
}
/// Inserts a [RepositoryConfig] into this manager.
///
/// If the manager did not have a [RepositoryConfig] with a corresponding repository url for
/// the repository, `None` is returned.
///
/// If the manager did have this repository present as a dynamic config, the value is replaced
/// and the old [RepositoryConfig] is returned. If this repository is a static config, the
/// static config is shadowed by the dynamic config until it is removed.
pub fn insert(&mut self, config: RepositoryConfig) -> Option<RepositoryConfig> {
let result = self.dynamic_configs.insert(config.repo_url().clone(), config);
self.save();
result
}
/// Removes a [RepositoryConfig] identified by the config `repo_url`.
pub fn remove(
&mut self,
repo_url: &RepoUri,
) -> Result<Option<RepositoryConfig>, CannotRemoveStaticRepositories> {
if let Some(config) = self.dynamic_configs.remove(repo_url) {
self.save();
return Ok(Some(config));
}
if self.static_configs.get(repo_url).is_some() {
Err(CannotRemoveStaticRepositories)
} else {
Ok(None)
}
}
/// Returns an iterator over all the managed [RepositoryConfig]s.
pub fn list(&self) -> List {
let keys = self
.dynamic_configs
.iter()
.chain(self.static_configs.iter())
.map(|(k, _)| k)
.collect::<BTreeSet<_>>();
List { keys: keys.into_iter(), repo_mgr: self }
}
/// If persistent dynamic configs are enabled, save the current configs to disk. Log, and
/// ultimately ignore, any errors that occur to make sure forward progress can always be made.
fn save(&self) {
let configs = self.dynamic_configs.iter().map(|(_, c)| c.clone()).collect::<Vec<_>>();
let result = (|| {
let mut temp_path = self.dynamic_configs_path.clone().into_os_string();
temp_path.push(".new");
let temp_path = PathBuf::from(temp_path);
{
let f = fs::File::create(&temp_path)?;
serde_json::to_writer(f, &RepositoryConfigs::Version1(configs))?;
}
fs::rename(temp_path, &self.dynamic_configs_path)
})();
match result {
Ok(()) => {}
Err(err) => {
fx_log_err!("error while saving repositories: {}", err);
}
}
}
}
/// [RepositoryManagerBuilder] constructs a [RepositoryManager], optionally initializing it
/// with [RepositoryConfig]s passed in directly or loaded out of the filesystem.
#[derive(Clone, Debug)]
pub struct RepositoryManagerBuilder {
dynamic_configs_path: PathBuf,
static_configs: HashMap<RepoUri, RepositoryConfig>,
dynamic_configs: HashMap<RepoUri, RepositoryConfig>,
}
impl RepositoryManagerBuilder {
/// Create a new builder and initialize it with the dynamic
/// [RepositoryConfigs](RepositoryConfig) from this path if it exists, and add it to the
/// [RepositoryManager], or error out if we encounter errors during the load. The
/// [RepositoryManagerBuilder] is also returned on error in case the errors should be ignored.
pub fn new<T>(dynamic_configs_path: T) -> Result<Self, (Self, LoadError)>
where
T: Into<PathBuf>,
{
let dynamic_configs_path = dynamic_configs_path.into();
let (dynamic_configs, err) = if dynamic_configs_path.exists() {
match load_configs_file(&dynamic_configs_path) {
Ok(dynamic_configs) => (dynamic_configs, None),
Err(err) => (vec![], Some(err)),
}
} else {
(vec![], None)
};
let builder = RepositoryManagerBuilder {
dynamic_configs_path: dynamic_configs_path.into(),
static_configs: HashMap::new(),
dynamic_configs: dynamic_configs
.into_iter()
.map(|config| (config.repo_url().clone(), config))
.collect(),
};
if let Some(err) = err {
Err((builder, err))
} else {
Ok(builder)
}
}
/// Adds these static [RepoConfigs](RepoConfig) to the [RepositoryManager].
#[cfg(test)]
pub fn static_configs<T>(mut self, iter: T) -> Self
where
T: IntoIterator<Item = RepositoryConfig>,
{
for config in iter.into_iter() {
self.static_configs.insert(config.repo_url().clone(), config);
}
self
}
/// Load a directory of [RepositoryConfigs](RepositoryConfig) files into the
/// [RepositoryManager], or error out if we encounter errors during the load. The
/// [RepositoryManagerBuilder] is also returned on error in case the errors should be ignored.
pub fn load_static_configs_dir<T>(
mut self,
static_configs_dir: T,
) -> Result<Self, (Self, Vec<LoadError>)>
where
T: AsRef<Path>,
{
let static_configs_dir = static_configs_dir.as_ref();
let (static_configs, errs) = load_configs_dir(static_configs_dir);
self.static_configs = static_configs;
if errs.is_empty() {
Ok(self)
} else {
Err((self, errs))
}
}
/// Build the [RepositoryManager].
pub fn build(self) -> RepositoryManager {
RepositoryManager {
dynamic_configs_path: self.dynamic_configs_path,
static_configs: self.static_configs,
dynamic_configs: self.dynamic_configs,
}
}
}
/// Load a directory of [RepositoryConfigs] files into a [RepositoryManager], or error out if we
/// encounter io errors during the load. It returns a [RepositoryManager], as well as all the
/// individual [LoadError] errors encountered during the load.
fn load_configs_dir<T: AsRef<Path>>(
dir: T,
) -> (HashMap<RepoUri, RepositoryConfig>, Vec<LoadError>) {
let dir = dir.as_ref();
let mut entries = match dir.read_dir() {
Ok(entries) => {
let entries: Result<Vec<_>, _> = entries.collect();
match entries {
Ok(entries) => entries,
Err(err) => {
return (HashMap::new(), vec![LoadError::Io { path: dir.into(), error: err }]);
}
}
}
Err(err) => {
return (HashMap::new(), vec![LoadError::Io { path: dir.into(), error: err }]);
}
};
// Make sure we always process entries in order to make config loading order deterministic.
entries.sort_by_key(|e| e.file_name());
let mut map = HashMap::new();
let mut errors = Vec::new();
for entry in entries {
let path = entry.path();
// Skip over any directories in this path.
match entry.file_type() {
Ok(file_type) => {
if !file_type.is_file() {
continue;
}
}
Err(err) => {
errors.push(LoadError::Io { path, error: err });
continue;
}
}
let expected_uri = path
.file_stem()
.and_then(|name| name.to_str())
.and_then(|name| RepoUri::new(name.to_string()).ok());
let configs = match load_configs_file(&path) {
Ok(configs) => configs,
Err(err) => {
errors.push(err);
continue;
}
};
// Insert the configs in filename lexographical order, and treating any duplicated
// configs as a recoverable error. As a special case, if the file the config comes from
// happens to be named the same as the repository hostname, use that config over some
// other config that came from some other file.
for config in configs {
match map.entry(config.repo_url().clone()) {
Entry::Occupied(mut entry) => {
let replaced_config = if Some(entry.key()) == expected_uri.as_ref() {
entry.insert(config)
} else {
config
};
errors.push(LoadError::Overridden { replaced_config });
}
Entry::Vacant(entry) => {
entry.insert(config);
}
}
}
}
(map, errors)
}
fn load_configs_file<T: AsRef<Path>>(path: T) -> Result<Vec<RepositoryConfig>, LoadError> {
let path = path.as_ref();
match fs::File::open(&path) {
Ok(f) => match serde_json::from_reader(f) {
Ok(RepositoryConfigs::Version1(configs)) => Ok(configs),
Err(err) => Err(LoadError::Parse { path: path.into(), error: err }),
},
Err(err) => Err(LoadError::Io { path: path.into(), error: err }),
}
}
/// [LoadError] describes all the recoverable error conditions that can be encountered when
/// parsing a [RepositoryConfigs] struct from a directory.
#[derive(Debug, Fail)]
pub enum LoadError {
/// This [std::io::Error] error occurred while reading the file.
Io {
path: PathBuf,
#[cause]
error: io::Error,
},
/// This file failed to parse into a valid [RepositoryConfigs].
Parse {
path: PathBuf,
#[cause]
error: serde_json::Error,
},
/// This [RepositoryManager] already contains a config for this repo_url.
Overridden { replaced_config: RepositoryConfig },
}
impl fmt::Display for LoadError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
LoadError::Io { path, error } => {
write!(f, "file {} failed to parse: {}", path.display(), error)
}
LoadError::Parse { path, error } => {
write!(f, "file {} failed to parse: {}", path.display(), error)
}
LoadError::Overridden { replaced_config } => {
write!(f, "repository config for {} was overridden", replaced_config.repo_url())
}
}
}
}
/// `List` is an iterator over all the [RepoConfig].
///
/// See its documentation for more.
pub struct List<'a> {
keys: btree_set::IntoIter<&'a RepoUri>,
repo_mgr: &'a RepositoryManager,
}
impl<'a> Iterator for List<'a> {
type Item = &'a RepositoryConfig;
fn next(&mut self) -> Option<Self::Item> {
if let Some(key) = self.keys.next() {
self.repo_mgr.get(key)
} else {
None
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Fail)]
#[fail(display = "cannot remove static repositories")]
pub struct CannotRemoveStaticRepositories;
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::create_dir;
use fidl_fuchsia_pkg_ext::{RepositoryConfigBuilder, RepositoryKey};
use fuchsia_uri::pkg_uri::RepoUri;
use maplit::hashmap;
use std::fs::File;
use std::io::Write;
fn assert_does_not_exist_error(err: &LoadError, missing_path: &Path) {
match &err {
LoadError::Io { path, error } => {
assert_eq!(path, missing_path);
assert_eq!(error.kind(), std::io::ErrorKind::NotFound, "{}", error);
}
err => {
panic!("unexpected error: {}", err);
}
}
}
fn assert_parse_error(err: &LoadError, invalid_path: &Path) {
match err {
LoadError::Parse { path, .. } => {
assert_eq!(path, invalid_path);
}
err => {
panic!("unexpected error: {}", err);
}
}
}
fn assert_overridden_error(err: &LoadError, config: &RepositoryConfig) {
match err {
LoadError::Overridden { replaced_config } => {
assert_eq!(replaced_config, config);
}
err => {
panic!("unexpected error: {}", err);
}
}
}
#[test]
fn test_insert_get_remove() {
let dynamic_dir = tempfile::tempdir().unwrap();
let dynamic_configs_path = dynamic_dir.path().join("config");
let mut repomgr = RepositoryManagerBuilder::new(&dynamic_configs_path).unwrap().build();
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path: dynamic_configs_path.clone(),
static_configs: HashMap::new(),
dynamic_configs: HashMap::new(),
}
);
let fuchsia_uri = RepoUri::parse("fuchsia-pkg://fuchsia.com").unwrap();
assert_eq!(repomgr.get(&fuchsia_uri), None);
let config1 = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![0]))
.build();
let config2 = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![1]))
.build();
assert_eq!(repomgr.insert(config1.clone()), None);
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path: dynamic_configs_path.clone(),
static_configs: HashMap::new(),
dynamic_configs: hashmap! {
fuchsia_uri.clone() => config1.clone(),
},
}
);
assert_eq!(repomgr.insert(config2.clone()), Some(config1.clone()));
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path: dynamic_configs_path.clone(),
static_configs: HashMap::new(),
dynamic_configs: hashmap! {
fuchsia_uri.clone() => config2.clone(),
},
}
);
assert_eq!(repomgr.get(&fuchsia_uri), Some(&config2));
assert_eq!(repomgr.remove(&fuchsia_uri), Ok(Some(config2.clone())));
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path: dynamic_configs_path.clone(),
static_configs: HashMap::new(),
dynamic_configs: HashMap::new()
}
);
assert_eq!(repomgr.remove(&fuchsia_uri), Ok(None));
}
#[test]
fn shadowing_static_config() {
let fuchsia_uri = RepoUri::parse("fuchsia-pkg://fuchsia.com").unwrap();
let fuchsia_config1 = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![1]))
.build();
let fuchsia_config2 = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![2]))
.build();
let static_dir = create_dir(vec![(
"fuchsia.com.json",
RepositoryConfigs::Version1(vec![fuchsia_config1.clone()]),
)]);
let dynamic_dir = tempfile::tempdir().unwrap();
let mut repomgr = RepositoryManagerBuilder::new(dynamic_dir.path().join("config"))
.unwrap()
.load_static_configs_dir(static_dir.path())
.unwrap()
.build();
assert_eq!(repomgr.get(&fuchsia_uri), Some(&fuchsia_config1));
assert_eq!(repomgr.insert(fuchsia_config2.clone()), None);
assert_eq!(repomgr.get(&fuchsia_uri), Some(&fuchsia_config2));
assert_eq!(repomgr.remove(&fuchsia_uri), Ok(Some(fuchsia_config2)));
assert_eq!(repomgr.get(&fuchsia_uri), Some(&fuchsia_config1));
}
#[test]
fn cannot_remove_static_config() {
let fuchsia_uri = RepoUri::parse("fuchsia-pkg://fuchsia.com").unwrap();
let fuchsia_config1 = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![1]))
.build();
let static_dir = create_dir(vec![(
"fuchsia.com.json",
RepositoryConfigs::Version1(vec![fuchsia_config1.clone()]),
)]);
let dynamic_dir = tempfile::tempdir().unwrap();
let dynamic_configs_path = dynamic_dir.path().join("config");
let mut repomgr = RepositoryManagerBuilder::new(&dynamic_configs_path)
.unwrap()
.load_static_configs_dir(static_dir.path())
.unwrap()
.build();
assert_eq!(repomgr.get(&fuchsia_uri), Some(&fuchsia_config1));
assert_eq!(repomgr.remove(&fuchsia_uri), Err(CannotRemoveStaticRepositories));
assert_eq!(repomgr.get(&fuchsia_uri), Some(&fuchsia_config1));
}
#[test]
fn test_builder_static_configs_dir_not_exists() {
let dynamic_dir = tempfile::tempdir().unwrap();
let dynamic_configs_path = dynamic_dir.path().join("config");
let static_dir = tempfile::tempdir().unwrap();
let does_not_exist_dir = static_dir.path().join("not-exists");
let (_, errors) = RepositoryManagerBuilder::new(&dynamic_configs_path)
.unwrap()
.load_static_configs_dir(&does_not_exist_dir)
.unwrap_err();
assert_eq!(errors.len(), 1, "{:?}", errors);
assert_does_not_exist_error(&errors[0], &does_not_exist_dir);
}
#[test]
fn test_builder_static_configs_dir_invalid_config() {
let dir = tempfile::tempdir().unwrap();
let invalid_path = dir.path().join("invalid");
let example_uri = RepoUri::parse("fuchsia-pkg://example.com").unwrap();
let example_config = RepositoryConfigBuilder::new(example_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![0]))
.build();
let fuchsia_uri = RepoUri::parse("fuchsia-pkg://fuchsia.com").unwrap();
let fuchsia_config = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![1]))
.build();
{
let mut f = File::create(&invalid_path).unwrap();
f.write(b"hello world").unwrap();
let f = File::create(dir.path().join("a")).unwrap();
serde_json::to_writer(f, &RepositoryConfigs::Version1(vec![example_config.clone()]))
.unwrap();
let f = File::create(dir.path().join("z")).unwrap();
serde_json::to_writer(f, &RepositoryConfigs::Version1(vec![fuchsia_config.clone()]))
.unwrap();
}
let dynamic_dir = tempfile::tempdir().unwrap();
let dynamic_configs_path = dynamic_dir.path().join("config");
let (builder, errors) = RepositoryManagerBuilder::new(&dynamic_configs_path)
.unwrap()
.load_static_configs_dir(dir.path())
.unwrap_err();
assert_eq!(errors.len(), 1, "{:?}", errors);
assert_parse_error(&errors[0], &invalid_path);
let repomgr = builder.build();
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path: dynamic_configs_path,
static_configs: hashmap! {
example_uri => example_config,
fuchsia_uri => fuchsia_config,
},
dynamic_configs: HashMap::new(),
}
);
}
#[test]
fn test_builder_static_configs_dir() {
let fuchsia_uri = RepoUri::parse("fuchsia-pkg://fuchsia.com").unwrap();
let fuchsia_config = RepositoryConfigBuilder::new(fuchsia_uri.clone()).build();
let example_uri = RepoUri::parse("fuchsia-pkg://example.com").unwrap();
let example_config = RepositoryConfigBuilder::new(example_uri.clone()).build();
let dir = create_dir(vec![
("example.com.json", RepositoryConfigs::Version1(vec![example_config.clone()])),
("fuchsia.com.json", RepositoryConfigs::Version1(vec![fuchsia_config.clone()])),
]);
let dynamic_dir = tempfile::tempdir().unwrap();
let dynamic_configs_path = dynamic_dir.path().join("config");
let repomgr = RepositoryManagerBuilder::new(&dynamic_configs_path)
.unwrap()
.load_static_configs_dir(dir.path())
.unwrap()
.build();
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path: dynamic_configs_path,
static_configs: hashmap! {
example_uri => example_config,
fuchsia_uri => fuchsia_config,
},
dynamic_configs: HashMap::new(),
}
);
}
#[test]
fn test_builder_static_configs_dir_overlapping_filename_wins() {
let fuchsia_uri = RepoUri::parse("fuchsia-pkg://fuchsia.com").unwrap();
let fuchsia_config = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![0]))
.build();
let fuchsia_com_config = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![1]))
.build();
let fuchsia_com_json_config = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![2]))
.build();
let example_config = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![3]))
.build();
let oem_config = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![4]))
.build();
// Even though the example file comes first, the fuchsia repo should take priority over the
// example file.
let dir = create_dir(vec![
("fuchsia", RepositoryConfigs::Version1(vec![fuchsia_config.clone()])),
("fuchsia.com", RepositoryConfigs::Version1(vec![fuchsia_com_config.clone()])),
("example.com.json", RepositoryConfigs::Version1(vec![example_config.clone()])),
(
"fuchsia.com.json",
RepositoryConfigs::Version1(vec![
oem_config.clone(),
fuchsia_com_json_config.clone(),
]),
),
]);
let dynamic_dir = tempfile::tempdir().unwrap();
let dynamic_configs_path = dynamic_dir.path().join("config");
let (builder, errors) = RepositoryManagerBuilder::new(&dynamic_configs_path)
.unwrap()
.load_static_configs_dir(dir.path())
.unwrap_err();
assert_eq!(errors.len(), 4);
assert_overridden_error(&errors[0], &fuchsia_config);
assert_overridden_error(&errors[1], &fuchsia_com_config);
assert_overridden_error(&errors[2], &example_config);
assert_overridden_error(&errors[3], &oem_config);
let repomgr = builder.build();
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path: dynamic_configs_path,
static_configs: hashmap! {
fuchsia_uri => fuchsia_com_json_config,
},
dynamic_configs: HashMap::new(),
}
);
}
#[test]
fn test_builder_static_configs_dir_overlapping_first_wins() {
let fuchsia_uri = RepoUri::parse("fuchsia-pkg://fuchsia.com").unwrap();
let fuchsia_config1 = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![0]))
.build();
let fuchsia_config2 = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![1]))
.build();
// Even though the example file comes first, the fuchsia repo should take priority over the
// example file.
let dir = create_dir(vec![
("1", RepositoryConfigs::Version1(vec![fuchsia_config1.clone()])),
("2", RepositoryConfigs::Version1(vec![fuchsia_config2.clone()])),
]);
let dynamic_dir = tempfile::tempdir().unwrap();
let dynamic_configs_path = dynamic_dir.path().join("config");
let (builder, errors) = RepositoryManagerBuilder::new(&dynamic_configs_path)
.unwrap()
.load_static_configs_dir(dir.path())
.unwrap_err();
assert_eq!(errors.len(), 1);
assert_overridden_error(&errors[0], &fuchsia_config2);
let repomgr = builder.build();
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path: dynamic_configs_path,
static_configs: hashmap! {
fuchsia_uri => fuchsia_config1,
},
dynamic_configs: HashMap::new(),
}
);
}
#[test]
fn test_builder_dynamic_configs_path_ignores_if_not_exists() {
let dynamic_dir = tempfile::tempdir().unwrap();
let dynamic_configs_path = dynamic_dir.path().join("config");
let repomgr = RepositoryManagerBuilder::new(&dynamic_configs_path).unwrap().build();
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path: dynamic_configs_path,
static_configs: HashMap::new(),
dynamic_configs: HashMap::new(),
}
);
}
#[test]
fn test_builder_dynamic_configs_path_invalid_config() {
let dir = tempfile::tempdir().unwrap();
let invalid_path = dir.path().join("invalid");
{
let mut f = File::create(&invalid_path).unwrap();
f.write(b"hello world").unwrap();
}
let (builder, err) = RepositoryManagerBuilder::new(&invalid_path).unwrap_err();
assert_parse_error(&err, &invalid_path);
let repomgr = builder.build();
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path: invalid_path,
static_configs: HashMap::new(),
dynamic_configs: HashMap::new(),
}
);
}
#[test]
fn test_builder_dynamic_configs_path() {
let fuchsia_uri = RepoUri::parse("fuchsia-pkg://fuchsia.com").unwrap();
let config = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![0]))
.build();
let dynamic_dir =
create_dir(vec![("config", RepositoryConfigs::Version1(vec![config.clone()]))]);
let dynamic_configs_path = dynamic_dir.path().join("config");
let repomgr = RepositoryManagerBuilder::new(&dynamic_configs_path).unwrap().build();
assert_eq!(
repomgr,
RepositoryManager {
dynamic_configs_path,
static_configs: HashMap::new(),
dynamic_configs: hashmap! {
fuchsia_uri => config,
},
}
);
}
#[test]
fn test_persistence() {
let fuchsia_uri = RepoUri::parse("fuchsia-pkg://fuchsia.com").unwrap();
let static_config = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![1]))
.build();
let static_configs = RepositoryConfigs::Version1(vec![static_config.clone()]);
let static_dir = create_dir(vec![("config", static_configs.clone())]);
let old_dynamic_config = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![2]))
.build();
let old_dynamic_configs = RepositoryConfigs::Version1(vec![old_dynamic_config.clone()]);
let dynamic_dir = create_dir(vec![("config", old_dynamic_configs.clone())]);
let dynamic_configs_path = dynamic_dir.path().join("config");
let mut repomgr = RepositoryManagerBuilder::new(&dynamic_configs_path)
.unwrap()
.load_static_configs_dir(&static_dir)
.unwrap()
.build();
// make sure the dynamic config file didn't change just from opening it.
let f = File::open(&dynamic_configs_path).unwrap();
let actual: RepositoryConfigs = serde_json::from_reader(f).unwrap();
assert_eq!(actual, old_dynamic_configs);
let new_dynamic_config = RepositoryConfigBuilder::new(fuchsia_uri.clone())
.add_root_key(RepositoryKey::Ed25519(vec![3]))
.build();
let new_dynamic_configs = RepositoryConfigs::Version1(vec![new_dynamic_config.clone()]);
// Inserting a new repo should update the config file.
assert_eq!(repomgr.insert(new_dynamic_config.clone()), Some(old_dynamic_config));
let f = File::open(&dynamic_configs_path).unwrap();
let actual: RepositoryConfigs = serde_json::from_reader(f).unwrap();
assert_eq!(actual, new_dynamic_configs);
// Removing the repo should empty out the file.
assert_eq!(repomgr.remove(&fuchsia_uri), Ok(Some(new_dynamic_config)));
let f = File::open(&dynamic_configs_path).unwrap();
let actual: RepositoryConfigs = serde_json::from_reader(f).unwrap();
assert_eq!(actual, RepositoryConfigs::Version1(vec![]));
// We should now be back to the static config.
assert_eq!(repomgr.get(&fuchsia_uri), Some(&static_config));
assert_eq!(repomgr.remove(&fuchsia_uri), Err(CannotRemoveStaticRepositories));
}
#[test]
fn test_list_empty() {
let dynamic_dir = tempfile::tempdir().unwrap();
let dynamic_configs_path = dynamic_dir.path().join("config");
let repomgr = RepositoryManagerBuilder::new(&dynamic_configs_path).unwrap().build();
assert_eq!(repomgr.list().collect::<Vec<_>>(), Vec::<&RepositoryConfig>::new());
}
#[test]
fn test_list() {
let example_uri = RepoUri::parse("fuchsia-pkg://example.com").unwrap();
let example_config = RepositoryConfigBuilder::new(example_uri).build();
let fuchsia_uri = RepoUri::parse("fuchsia-pkg://fuchsia.com").unwrap();
let fuchsia_config = RepositoryConfigBuilder::new(fuchsia_uri).build();
let static_dir = create_dir(vec![
("example.com", RepositoryConfigs::Version1(vec![example_config.clone()])),
("fuchsia.com", RepositoryConfigs::Version1(vec![fuchsia_config.clone()])),
]);
let dynamic_dir = tempfile::tempdir().unwrap();
let dynamic_configs_path = dynamic_dir.path().join("config");
let repomgr = RepositoryManagerBuilder::new(&dynamic_configs_path)
.unwrap()
.load_static_configs_dir(static_dir.path())
.unwrap()
.build();
assert_eq!(repomgr.list().collect::<Vec<_>>(), vec![&example_config, &fuchsia_config,]);
}
}