A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

336 lines
9.5 KiB

use super::core::{RegularSetupCore, SetupCore};
use super::runnable::Runnable;
use super::util::{AddResult, AddableResource, InternalAddResult};
use super::SymbolRunner;
use crate::async_utils::sleep;
use crate::loggers::{Logger, StoringLogger};
use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource};
use crate::{DefaultBuilder, DefaultLocator};
use futures_util::future::{FutureExt, Shared};
use std::cell::{RefCell, RefMut};
use std::collections::HashMap;
use std::error::Error;
use std::future::Future;
use std::hash::Hash;
use std::pin::Pin;
use std::rc::Rc;
use std::time::Duration;
type Cache<Rs, As> = HashMap<Rs, Shared<Pin<Box<dyn Future<Output = (As, bool)>>>>>;
#[derive(Debug)]
struct SetupInner<CORE, Rs, As> {
core: CORE,
resources: RefCell<Cache<Rs, As>>,
}
#[derive(Debug)]
pub struct Setup<SR, L, B, Rs, As>(Rc<SetupInner<RegularSetupCore<SR, L, B>, Rs, As>>);
impl<L: 'static, B: 'static, SR: 'static, Rs: Hash + Eq + 'static, As: 'static>
Setup<SR, L, B, Rs, As>
{
fn borrow_resources(&self) -> RefMut<'_, Cache<Rs, As>> {
self.0.resources.borrow_mut()
}
pub async fn add<R: AddableResource>(&self, resource: R, force_run: bool) -> InternalAddResult<R>
where
Rs: FromResource<R>,
As: FromArtifact<R> + Clone,
R::Artifact: Clone,
RegularSetupCore<SR, L, B>: SetupCore<R, Self>,
{
let (storable_resource, weak_resource) = Rs::from_resource(resource);
let mut resources = self.borrow_resources();
let result = if let Some(future) = resources.remove(&storable_resource) {
assert!(
!force_run,
"Forcing to run an already-added resource is a logical error"
);
resources.insert(storable_resource, future.clone());
drop(resources);
let logger = StoringLogger::default();
logger.trace(format!(
"{:?} already added",
weak_resource.upgrade().expect("Dangling!")
));
let (t, did_run) = future.await;
Ok((logger, t, did_run))
} else {
let inner_weak = Rc::downgrade(&self.0);
let future = Box::pin(async move {
let this = Self(inner_weak.upgrade().expect("Dangling!"));
let resource = weak_resource.upgrade().expect("Dangling!");
// Need to convert Box<Error> to String for Clone for Shared
let result = this.0.core.add(&this, resource, force_run).await;
result
.map(|(logger, t, did_run)| (logger, As::from_artifact(t), did_run))
.map_err(|(logger, e)| (logger, e.to_string()))
})
.shared();
let future_clone = future.clone();
resources.insert(
storable_resource,
(Box::pin(async move {
let result = future_clone.await;
if result.is_err() {
// Step back to give the initial caller time to handle the error before unwrapping
sleep(Duration::from_millis(0)).await;
}
result.map(|(_, t, did_run)| (t, did_run)).unwrap()
}) as Pin<Box<dyn Future<Output = (As, bool)>>>)
.shared(),
);
drop(resources);
let result = future.await;
result.map_err(|(logger, e)| (logger, e.into()))
};
result.map(|(logger, t, did_run)| (logger, t.into_artifact(), did_run))
}
}
#[derive(Debug)]
pub struct SetupFacade<
SR,
LOG,
L = DefaultLocator,
B = DefaultBuilder,
Rs = DefaultResources<'static, &'static str>,
As = DefaultArtifacts<'static, &'static str>,
>(LOG, Setup<SR, L, B, Rs, As>);
impl<SR, LOG> SetupFacade<SR, LOG> {
pub fn new(symbol_runner: SR, logger: LOG) -> Self {
Self::new_with(symbol_runner, logger)
}
}
impl<L, B, As, SR, LOG, Rs: Hash + Eq> SetupFacade<SR, LOG, L, B, Rs, As> {
pub fn new_with(symbol_runner: SR, logger: LOG) -> Self {
Self(
logger,
Setup(Rc::new(SetupInner {
core: RegularSetupCore::new(symbol_runner),
resources: RefCell::default(),
})),
)
}
}
impl<
SR: 'static,
LOG: 'static + Logger,
L: 'static,
B: 'static,
Rs: 'static + Eq + Hash,
As: 'static,
> SetupFacade<SR, LOG, L, B, Rs, As>
{
pub async fn add_force<R: AddableResource>(&self, resource: R, force_run: bool) -> AddResult<R>
where
Rs: FromResource<R>,
As: FromArtifact<R> + Clone,
R::Artifact: Clone,
RegularSetupCore<SR, L, B>: SetupCore<R, Setup<SR, L, B, Rs, As>>,
{
let result = self.1.add(resource, force_run).await;
match result {
Ok((logger, t, did_run)) => {
if self
.0
.put(logger.release().into_iter().filter(|e| e.0 <= 3))
== 0
{
self.0.write(3, ".");
}
Ok((t, did_run))
}
Err((logger, e)) => {
self.0.put(logger.release());
Err(e)
}
}
}
pub async fn add<R: AddableResource>(&self, resource: R) -> AddResult<R>
where
RegularSetupCore<SR, L, B>: SetupCore<R, Setup<SR, L, B, Rs, As>>,
Rs: FromResource<R>,
As: FromArtifact<R> + Clone,
R::Artifact: Clone,
SR: SymbolRunner,
{
self.add_force(resource, false).await
}
pub async fn run_symbol<S: Runnable>(
&self,
symbol: S,
force: bool,
) -> Result<bool, Box<dyn Error>>
where
RegularSetupCore<SR, L, B>: SymbolRunner,
{
symbol.run(&(self.1).0.core, &self.0, force).await
}
}
#[cfg(test)]
mod test {
use super::SymbolRunner;
use crate::async_utils::run;
use crate::loggers::{Logger, StoringLogger};
use crate::resources::{FromArtifact, FromResource, Resource};
use crate::symbols::Symbol;
use crate::to_artifact::ToArtifact;
use crate::{ImplementationBuilder, ResourceLocator, Setup};
use async_trait::async_trait;
use std::cell::RefCell;
use std::error::Error;
use std::fmt::Debug;
use std::rc::{Rc, Weak};
struct TestSymbolRunner {
count: Rc<RefCell<usize>>,
}
#[async_trait(?Send)]
impl SymbolRunner for TestSymbolRunner {
async fn run_symbol<S: Symbol + Debug, L: Logger>(
&self,
symbol: &S,
_logger: &L,
force: bool,
) -> Result<bool, Box<dyn Error>> {
let run = force || !symbol.target_reached().await?;
if run {
*self.count.borrow_mut() += 1;
}
Ok(run)
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
struct TestResource<T>(&'static str, T);
impl<T> Resource for TestResource<T> {
type Artifact = ();
}
#[derive(Debug, Hash, PartialEq, Eq)]
enum Resources {
A(Rc<TestResource<&'static str>>),
B(Rc<TestResource<()>>),
}
impl FromResource<TestResource<&'static str>> for Resources {
fn from_resource(from: TestResource<&'static str>) -> (Self, Weak<TestResource<&'static str>>) {
let inner = Rc::new(from);
(Self::A(Rc::clone(&inner)), Rc::downgrade(&inner))
}
}
impl FromResource<TestResource<()>> for Resources {
fn from_resource(from: TestResource<()>) -> (Self, Weak<TestResource<()>>) {
let inner = Rc::new(from);
(Self::B(Rc::clone(&inner)), Rc::downgrade(&inner))
}
}
#[derive(Clone)]
struct Artifacts;
impl<V> FromArtifact<TestResource<V>> for Artifacts {
fn from_artifact(_from: ()) -> Self {
Self
}
#[allow(clippy::unused_unit)]
fn into_artifact(self) -> () {
#[allow(clippy::unused_unit)]
()
}
}
struct TestResourceLocator;
impl<T> ResourceLocator<TestResource<T>> for TestResourceLocator {
type Prerequisites = ();
fn locate(_resource: &TestResource<T>) -> (<TestResource<T> as ToArtifact>::Artifact, ()) {
((), ())
}
}
struct TestImplementationBuilder;
impl ImplementationBuilder<TestResource<&'static str>> for TestImplementationBuilder {
type Implementation = TestSymbol;
type Prerequisites = TestResource<()>;
fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites {
TestResource(resource.1, ())
}
fn create(
resource: &TestResource<&'static str>,
(): &(),
_inputs: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Implementation {
TestSymbol {
reached: resource.0.chars().next().unwrap().is_uppercase(),
}
}
}
impl ImplementationBuilder<TestResource<()>> for TestImplementationBuilder {
type Implementation = TestSymbol;
type Prerequisites = ();
fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {}
fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Implementation {
TestSymbol {
reached: resource.0.chars().next().unwrap().is_uppercase(),
}
}
}
#[derive(Debug)]
struct TestSymbol {
reached: bool,
}
#[async_trait(?Send)]
impl Symbol for TestSymbol {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(self.reached)
}
async fn execute(&self) -> Result<(), Box<dyn Error>> {
Ok(())
}
}
fn get_setup() -> (
Rc<RefCell<usize>>,
Setup<
TestSymbolRunner,
StoringLogger,
TestResourceLocator,
TestImplementationBuilder,
Resources,
Artifacts,
>,
) {
let count = Rc::new(RefCell::new(0));
let runner = TestSymbolRunner {
count: Rc::clone(&count),
};
(count, Setup::new_with(runner, StoringLogger::new()))
}
#[test]
fn correctly_uses_force() {
run(async {
let (count, setup) = get_setup();
setup.add(TestResource("A", "b")).await.unwrap();
assert_eq!(*count.borrow(), 2);
setup.add(TestResource("A", "b")).await.unwrap();
assert_eq!(*count.borrow(), 2);
let (count, setup) = get_setup();
setup.add(TestResource("A", "B")).await.unwrap();
assert_eq!(*count.borrow(), 0);
});
}
}