use compose_yml::v2 as dc;
use std::vec;
use crate::errors::*;
use crate::ext::context::ContextExt;
#[cfg(test)]
use crate::project::Project;
use crate::sources::{self, Source};
use crate::util::err;
pub trait ServiceExt {
fn context(&self) -> Result<Option<&dc::Context>>;
fn source_mount_dir(&self) -> Result<String>;
fn repository_subdirectory(&self) -> Result<Option<String>>;
fn shell(&self) -> Result<String>;
fn test_command(&self) -> Result<Vec<String>>;
fn sources<'a, 'b>(&'a self, sources: &'b sources::Sources)
-> Result<Sources<'b>>;
}
impl ServiceExt for dc::Service {
fn context(&self) -> Result<Option<&dc::Context>> {
if let Some(ref build) = self.build {
Ok(Some(build.context.value()?))
} else {
Ok(None)
}
}
fn source_mount_dir(&self) -> Result<String> {
let default = dc::escape("/app")?;
let srcdir = self.labels.get("io.fdy.cage.srcdir").unwrap_or(&default);
Ok(srcdir.value()?.to_owned())
}
fn repository_subdirectory(&self) -> Result<Option<String>> {
if let Some(context) = self.context()? {
return match *context {
dc::Context::Dir(_) => Ok(None),
dc::Context::GitUrl(ref git_url) => {
Ok(git_url.subdirectory().map(|subdir| subdir.to_string()))
}
};
}
Ok(None)
}
fn shell(&self) -> Result<String> {
let default = dc::escape("sh")?;
let shell = self.labels.get("io.fdy.cage.shell").unwrap_or(&default);
Ok(shell.value()?.to_owned())
}
fn test_command(&self) -> Result<Vec<String>> {
let raw = self.labels.get("io.fdy.cage.test").ok_or_else(|| {
err("specify a value for the label io.fdy.cage.test to run tests")
})?;
let mut lexer = shlex::Shlex::new(raw.value()?);
let result: Vec<String> = lexer.by_ref().collect();
if lexer.had_error {
Err(err!("cannot parse <{}> into shell words", raw))
} else {
Ok(result)
}
}
fn sources<'a, 'b>(
&'a self,
sources: &'b sources::Sources,
) -> Result<Sources<'b>> {
let container_path = self.source_mount_dir()?;
let source_subdirectory = self.repository_subdirectory()?;
let context = self
.context()?
.and_then(|ctx| {
let alias = &ctx
.human_alias()
.expect("human_alias failed on a context that worked previously");
sources.find_by_alias(alias)
})
.map(|source| SourceMount {
container_path,
source,
source_subdirectory,
});
let mut libs = vec![];
for (label, mount_as) in &self.labels {
let prefix = "io.fdy.cage.lib.";
if let Some(lib_name) = label.strip_prefix(prefix) {
let source =
sources.find_by_lib_key(lib_name).ok_or_else(|| -> Error {
ErrorKind::UnknownLibKey(lib_name.to_string()).into()
})?;
libs.push(SourceMount {
container_path: mount_as.value()?.to_owned(),
source,
source_subdirectory: None,
})
}
}
Ok(Sources {
context,
libs: libs.into_iter(),
})
}
}
pub struct Sources<'a> {
context: Option<SourceMount<'a>>,
libs: vec::IntoIter<SourceMount<'a>>,
}
#[derive(Clone)]
pub struct SourceMount<'a> {
pub container_path: String,
pub source: &'a Source,
pub source_subdirectory: Option<String>,
}
impl<'a> Iterator for Sources<'a> {
type Item = SourceMount<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.context.take().or_else(|| {
self.libs.next()
})
}
}
#[test]
fn src_dir_returns_the_source_directory_for_this_service() {
let _ = env_logger::try_init();
let proj: Project = Project::from_example("rails_hello").unwrap();
let target = proj.target("development").unwrap();
let db = proj.pod("db").unwrap();
let merged = db.merged_file(target).unwrap();
let db = merged.services.get("db").unwrap();
assert_eq!(db.source_mount_dir().unwrap(), "/app");
let frontend = proj.pod("frontend").unwrap();
let merged = frontend.merged_file(target).unwrap();
let proxy = merged.services.get("web").unwrap();
assert_eq!(proxy.source_mount_dir().unwrap(), "/usr/src/app");
}
#[test]
fn build_context_can_specify_a_subdirectory() {
let _ = env_logger::try_init();
let proj: Project = Project::from_fixture("with_repo_subdir").unwrap();
let target = proj.target("development").unwrap();
let db = proj.pod("db").unwrap();
let merged = db.merged_file(target).unwrap();
let db = merged.services.get("db").unwrap();
assert_eq!(db.repository_subdirectory().unwrap(), None);
let frontend = proj.pod("frontend").unwrap();
let merged = frontend.merged_file(target).unwrap();
let web = merged.services.get("web").unwrap();
assert_eq!(
web.repository_subdirectory().unwrap(),
Some("myfolder".to_string())
);
}
#[test]
fn shell_returns_preferred_shell_for_this_service() {
let _ = env_logger::try_init();
let proj: Project = Project::from_example("hello").unwrap();
let target = proj.target("development").unwrap();
let frontend = proj.pod("frontend").unwrap();
let merged = frontend.merged_file(target).unwrap();
let web = merged.services.get("web").unwrap();
assert_eq!(web.shell().unwrap(), "sh");
let proxy = merged.services.get("proxy").unwrap();
assert_eq!(proxy.shell().unwrap(), "/bin/sh");
}