mod cli;
use crate::{
Inventory,
types::{ExePlay, ExePlaybook, StackName},
};
use anyhow::Result;
use cli::Cli;
use indexmap::IndexMap;
pub struct App {
args: Vec<String>,
stacks: IndexMap<StackName, Box<dyn Stack>>,
inventories: IndexMap<String, Inventory>,
#[doc(hidden)]
exe_playbooks: IndexMap<StackName, ExePlaybook>,
}
impl App {
pub fn new(args: Vec<String>) -> Self {
Self {
args,
inventories: IndexMap::new(),
stacks: IndexMap::new(),
exe_playbooks: IndexMap::new(),
}
}
pub fn add_inventory(&mut self, inventory: Inventory) -> Result<()> {
let old_inventory = self.inventories.insert(inventory.name.clone(), inventory);
if let Some(old_inventory) = old_inventory {
anyhow::bail!("conflicting inventory name: {}", old_inventory.name);
}
Ok(())
}
pub fn add_stack(&mut self, stack: Box<dyn Stack>) -> Result<()> {
let old_exe_playbook = self.exe_playbooks.insert(
stack.name().into(),
ExePlaybook::from_exe_play(stack.name(), stack.exe_play().clone()),
);
if let Some(old_exe_playbook) = old_exe_playbook {
anyhow::bail!(
"conflicting stack name: {} ({:?})",
stack.name(),
old_exe_playbook
);
}
let old_stack = self.stacks.insert(stack.name().into(), stack);
if let Some(old_stack) = old_stack {
anyhow::bail!("conflicting stack name: {}", old_stack.name());
}
Ok(())
}
pub fn run(&self) -> Result<()> {
let nprocs = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or_default();
let threads = nprocs; tokio::runtime::Builder::new_multi_thread()
.enable_all()
.worker_threads(threads)
.build()?
.block_on(Cli::run(self))
}
fn exe_playbooks(&self) -> &IndexMap<StackName, ExePlaybook> {
&self.exe_playbooks
}
}
pub trait Stack {
fn name(&self) -> &str;
fn exe_play(&self) -> &ExePlay;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::test::*;
#[test]
fn test_sample_stack() {
struct SampleStack {
exe_play: ExePlay,
}
impl SampleStack {
fn new() -> Self {
Self {
exe_play: create_play_helper("sample").into(),
}
}
}
impl Stack for SampleStack {
fn name(&self) -> &str {
::std::any::type_name::<Self>()
}
fn exe_play(&self) -> &ExePlay {
&self.exe_play
}
}
let mut app = App::new(vec!["help".to_string()]);
app.add_stack(Box::new(SampleStack::new()))
.expect("Failed to add sample stack");
}
#[test]
fn test_stack_name_confliction() {
struct SampleStack1 {
name: String,
exe_play: ExePlay,
}
impl SampleStack1 {
fn new(n: &str) -> Self {
Self {
name: n.to_string(),
exe_play: create_play_helper("sample1").into(),
}
}
}
impl Stack for SampleStack1 {
fn name(&self) -> &str {
&self.name
}
fn exe_play(&self) -> &ExePlay {
&self.exe_play
}
}
struct SampleStack2 {
name: String,
exe_play: ExePlay,
}
impl SampleStack2 {
fn new(n: &str) -> Self {
Self {
name: n.to_string(),
exe_play: create_play_helper("sample2").into(),
}
}
}
impl Stack for SampleStack2 {
fn name(&self) -> &str {
&self.name
}
fn exe_play(&self) -> &ExePlay {
&self.exe_play
}
}
let mut app = App::new(vec!["help".to_string()]);
app.add_stack(Box::new(SampleStack1::new("sample")))
.expect("Failed to add sample stack");
app.add_stack(Box::new(SampleStack2::new("sample")))
.expect_err("should be duplicated error");
}
}