use docker_compose::v2 as dc;
use std::collections::BTreeMap;
use std::collections::btree_map;
use std::path::{Path, PathBuf};
use command_runner::{Command, CommandRunner};
#[cfg(test)] use command_runner::TestCommandRunner;
use ext::service::ServiceExt;
#[cfg(test)] use std::fs;
use project::Project;
use pod::Pod;
use util::{ConductorPathExt, Error};
#[derive(Debug)]
pub struct Repos {
repos: BTreeMap<String, Repo>,
}
impl Repos {
#[doc(hidden)]
pub fn new(pods: &[Pod]) -> Result<Repos, Error> {
let mut repos: BTreeMap<String, Repo> = BTreeMap::new();
for pod in pods {
for file in pod.all_files() {
for (_name, service) in &file.services {
if let Some(git_url) = try!(service.git_url()).cloned() {
let url_path =
Path::new(try!(git_url.to_url()).path()).to_owned();
let alias = try!(url_path.file_stem().ok_or_else(|| {
err!("Can't get repo name from {}", &git_url)
})).to_str().unwrap().to_owned();
let repo = Repo { alias: alias, git_url: git_url };
match repos.entry(repo.alias.clone()) {
btree_map::Entry::Vacant(vacant) => {
vacant.insert(repo);
}
btree_map::Entry::Occupied(occupied) => {
if &repo.alias != &occupied.get().alias {
return Err(err!("{} and {} would both alias to {}",
&occupied.get().git_url,
&repo.git_url,
&repo.alias));
}
}
}
}
}
}
}
Ok(Repos { repos: repos })
}
pub fn iter(&self) -> Iter {
Iter { iter: self.repos.iter() }
}
pub fn find_by_alias(&self, alias: &str) -> Option<&Repo> {
self.repos.get(alias)
}
pub fn find_by_git_url(&self, url: &dc::GitUrl) -> Option<&Repo> {
self.repos.values().find(|r| r.git_url() == url)
}
}
#[derive(Clone)]
pub struct Iter<'a> {
iter: btree_map::Iter<'a, String, Repo>,
}
impl<'a> Iterator for Iter<'a> {
type Item = &'a Repo;
fn next(&mut self) -> Option<&'a Repo> {
self.iter.next().map(|(_alias, repo)| repo)
}
}
#[derive(Debug)]
pub struct Repo {
alias: String,
git_url: dc::GitUrl,
}
impl Repo {
pub fn alias(&self) -> &str {
&self.alias
}
pub fn git_url(&self) -> &dc::GitUrl {
&self.git_url
}
pub fn rel_path(&self) -> PathBuf {
Path::new(self.alias()).to_owned()
}
pub fn path(&self, project: &Project) -> PathBuf {
project.src_dir().join(self.rel_path())
}
pub fn is_cloned(&self, project: &Project) -> bool {
self.path(project).exists()
}
pub fn clone_source<CR>(&self, runner: &CR, project: &Project) ->
Result<(), Error>
where CR: CommandRunner
{
let dest = try!(self.path(project).with_guaranteed_parent());
let status = try!(runner.build("git")
.arg("clone")
.arg(self.git_url())
.arg(&dest)
.status());
if !status.success() {
return Err(err!("Error cloning {} to {}", &self.git_url,
dest.display()));
}
Ok(())
}
#[cfg(test)]
pub fn fake_clone_source(&self, project: &Project) -> Result<(), Error> {
try!(fs::create_dir_all(self.path(project)));
Ok(())
}
}
#[test]
fn are_loaded_with_projects() {
let proj = Project::from_example("hello").unwrap();
let repos = proj.repos();
assert_eq!(repos.iter().count(), 2);
let hello = repos.find_by_alias("dockercloud-hello-world")
.expect("repos should include dockercloud-hello-world");
assert_eq!(hello.alias(), "dockercloud-hello-world");
assert_eq!(hello.git_url().as_ref() as &str,
"https://github.com/docker/dockercloud-hello-world.git");
assert_eq!(hello.rel_path(), Path::new("dockercloud-hello-world"));
}
#[test]
fn can_be_cloned() {
let proj = Project::from_example("hello").unwrap();
let repo = proj.repos().find_by_alias("dockercloud-hello-world").unwrap();
let runner = TestCommandRunner::new();
repo.clone_source(&runner, &proj).unwrap();
assert_ran!(runner, {
["git", "clone", repo.git_url(),
proj.src_dir().join(repo.rel_path())]
});
proj.remove_test_output().unwrap();
}
#[test]
fn can_be_checked_to_see_if_cloned() {
let proj = Project::from_example("hello").unwrap();
let repo = proj.repos().find_by_alias("dockercloud-hello-world").unwrap();
assert!(!repo.is_cloned(&proj));
repo.fake_clone_source(&proj).unwrap();
assert!(repo.is_cloned(&proj));
proj.remove_test_output().unwrap();
}