use compose_yml::v2 as dc;
use std::fmt;
use std::io;
use std::marker::PhantomData;
use crate::errors::*;
use crate::pod::Pod;
use crate::project::Project;
use crate::template::Template;
pub mod transform;
#[derive(Debug)]
pub struct Context<'a> {
pub project: &'a Project,
pub pod: &'a Pod,
pub subcommand: String,
_nonexclusive: PhantomData<()>,
}
impl<'a> Context<'a> {
pub fn new(project: &'a Project, pod: &'a Pod, subcommand: &str) -> Context<'a> {
Context {
project,
pod,
subcommand: subcommand.to_string(),
_nonexclusive: PhantomData,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Operation {
Output,
Export,
}
pub trait Plugin: Sync {
fn name(&self) -> &'static str;
}
pub trait PluginNew: Plugin + Sized + fmt::Debug {
fn plugin_name() -> &'static str;
fn is_configured_for(_project: &Project) -> Result<bool> {
Ok(true)
}
fn new(project: &Project) -> Result<Self>;
}
pub trait PluginTransform: Plugin {
fn transform(
&self,
op: Operation,
ctx: &Context<'_>,
file: &mut dc::File,
) -> Result<()>;
}
pub trait PluginGenerate: Plugin {
fn generator_description(&self) -> &'static str;
fn generate(&self, project: &Project, out: &mut dyn io::Write) -> Result<()> {
let mut proj_tmpl = Template::new(self.name())?;
proj_tmpl.generate(project.root_dir(), project, out)?;
Ok(())
}
}
pub struct Manager {
transforms: Vec<Box<dyn PluginTransform>>,
generators: Vec<Box<dyn PluginGenerate>>,
}
impl Manager {
pub fn new(proj: &Project) -> Result<Manager> {
let mut manager = Manager {
transforms: vec![],
generators: vec![],
};
manager.register_generator::<transform::secrets::Plugin>(proj)?;
manager.register_generator::<transform::vault::Plugin>(proj)?;
manager.register_transform::<transform::abs_path::Plugin>(proj)?;
manager.register_transform::<transform::default_tags::Plugin>(proj)?;
manager.register_transform::<transform::host_dns::Plugin>(proj)?;
manager.register_transform::<transform::sources::Plugin>(proj)?;
manager.register_transform::<transform::secrets::Plugin>(proj)?;
manager.register_transform::<transform::remove_build::Plugin>(proj)?;
manager.register_transform::<transform::vault::Plugin>(proj)?;
manager.register_transform::<transform::labels::Plugin>(proj)?;
Ok(manager)
}
pub fn generators(&self) -> &[Box<dyn PluginGenerate>] {
&self.generators
}
fn new_plugin<T>(&self, proj: &Project) -> Result<T>
where
T: PluginNew + 'static,
{
T::new(proj).chain_err(|| ErrorKind::PluginFailed(T::plugin_name().to_owned()))
}
fn register_generator<T>(&mut self, proj: &Project) -> Result<()>
where
T: PluginNew + PluginGenerate + 'static,
{
let plugin: T = self.new_plugin(proj)?;
self.generators.push(Box::new(plugin));
Ok(())
}
fn register_transform<T>(&mut self, proj: &Project) -> Result<()>
where
T: PluginNew + PluginTransform + 'static,
{
if T::is_configured_for(proj)? {
let plugin: T = self.new_plugin(proj)?;
self.transforms.push(Box::new(plugin));
}
Ok(())
}
fn missing_plugin(&self, name: &str) -> ErrorKind {
if name == "vault" {
ErrorKind::FeatureDisabled
} else {
unreachable!("Cannot find a generator named {}", name)
}
}
pub fn generate(
&self,
project: &Project,
name: &str,
out: &mut dyn io::Write,
) -> Result<()> {
let generator = self
.generators
.iter()
.find(|g| g.name() == name)
.ok_or_else(|| self.missing_plugin(name))?;
debug!("Generating {}", generator.name());
generator.generate(project, out)
}
pub fn transform(
&self,
op: Operation,
ctx: &Context<'_>,
file: &mut dc::File,
) -> Result<()> {
for plugin in &self.transforms {
trace!("transforming '{}' with {}", ctx.pod.name(), plugin.name());
plugin
.transform(op, ctx, file)
.chain_err(|| ErrorKind::PluginFailed(plugin.name().to_owned()))?;
}
Ok(())
}
}
impl fmt::Debug for Manager {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut names: Vec<_> = vec![];
names.extend_from_slice(
&self.transforms.iter().map(|p| p.name()).collect::<Vec<_>>(),
);
write!(f, "plugins::Manager {{ {:?} }}", &names)
}
}