6 Commits

  1. 1
      Cargo.toml
  2. 26
      src/builder.rs
  3. 43
      src/command_runner.rs
  4. 12
      src/locator.rs
  5. 4
      src/resources/mod.rs
  6. 117
      src/setup/cache.rs
  7. 149
      src/setup/mod.rs
  8. 4
      src/symbols/cron.rs
  9. 73
      src/symbols/git/checkout.rs
  10. 81
      src/symbols/tls/csr.rs
  11. 218
      src/symbols/tls/key.rs
  12. 13
      src/symbols/user.rs

1
Cargo.toml

@ -19,3 +19,4 @@ nonzero_ext = "0.3.0"
[dev-dependencies] [dev-dependencies]
tempfile = "3" tempfile = "3"
mockall = "0.11.4"

26
src/builder.rs

@ -121,7 +121,7 @@ impl<D: Clone> ImplementationBuilder<Cert<D>> for DefaultBuilder {
fn create( fn create(
resource: &Cert<D>, resource: &Cert<D>,
target: &<Cert<D> as Resource>::Artifact, target: &<Cert<D> as Resource>::Artifact,
(csr, root_cert, account_key, challenges_dir, user_name, _): <Self::Prerequisites as ToArtifact>::Artifact,
(csr, root_cert, account_key, challenges_dir, (user_name, _), _): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Implementation { ) -> Self::Implementation {
CertSymbol::new( CertSymbol::new(
resource.0.clone(), resource.0.clone(),
@ -552,13 +552,13 @@ impl<D: Clone> ImplementationBuilder<UserForDomain<D>> for DefaultBuilder {
type Prerequisites = (); type Prerequisites = ();
fn prerequisites(_resource: &UserForDomain<D>) -> Self::Prerequisites {} fn prerequisites(_resource: &UserForDomain<D>) -> Self::Prerequisites {}
type Implementation = UserSymbol<Rc<str>, StdCommandRunner>;
type Implementation = UserSymbol<Rc<str>, Rc<Path>, StdCommandRunner>;
fn create( fn create(
_resource: &UserForDomain<D>, _resource: &UserForDomain<D>,
(user_name, _home_path): &<UserForDomain<D> as Resource>::Artifact,
(user_name, home_path): &<UserForDomain<D> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact, (): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Implementation { ) -> Self::Implementation {
UserSymbol::new(user_name.0.clone(), StdCommandRunner)
UserSymbol::new(user_name.0.clone(), home_path.into(), StdCommandRunner)
} }
} }
@ -566,13 +566,13 @@ impl ImplementationBuilder<User> for DefaultBuilder {
type Prerequisites = (); type Prerequisites = ();
fn prerequisites(_resource: &User) -> Self::Prerequisites {} fn prerequisites(_resource: &User) -> Self::Prerequisites {}
type Implementation = UserSymbol<Rc<str>, StdCommandRunner>;
type Implementation = UserSymbol<Rc<str>, Rc<Path>, StdCommandRunner>;
fn create( fn create(
resource: &User, resource: &User,
(): &<User as Resource>::Artifact,
home_path: &<User as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact, (): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Implementation { ) -> Self::Implementation {
UserSymbol::new(resource.0.clone(), StdCommandRunner)
UserSymbol::new(resource.0.clone(), home_path.into(), StdCommandRunner)
} }
} }
@ -594,13 +594,13 @@ impl ImplementationBuilder<AcmeUser> for DefaultBuilder {
type Prerequisites = (); type Prerequisites = ();
fn prerequisites(_resource: &AcmeUser) -> Self::Prerequisites {} fn prerequisites(_resource: &AcmeUser) -> Self::Prerequisites {}
type Implementation = UserSymbol<Rc<str>, StdCommandRunner>;
type Implementation = UserSymbol<Rc<str>, Rc<Path>, StdCommandRunner>;
fn create( fn create(
_resource: &AcmeUser, _resource: &AcmeUser,
user_name: &<AcmeUser as Resource>::Artifact,
(user_name, home_path): &<AcmeUser as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact, (): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Implementation { ) -> Self::Implementation {
UserSymbol::new(user_name.0.clone(), StdCommandRunner)
UserSymbol::new(user_name.0.clone(), home_path.into(), StdCommandRunner)
} }
} }
@ -617,7 +617,7 @@ impl ImplementationBuilder<AcmeChallengesDir> for DefaultBuilder {
fn create( fn create(
_resource: &AcmeChallengesDir, _resource: &AcmeChallengesDir,
target: &<AcmeChallengesDir as Resource>::Artifact, target: &<AcmeChallengesDir as Resource>::Artifact,
user_name: <Self::Prerequisites as ToArtifact>::Artifact,
(user_name, _): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Implementation { ) -> Self::Implementation {
( (
DirSymbol::new(target.clone_rc()), DirSymbol::new(target.clone_rc()),
@ -658,7 +658,7 @@ impl ImplementationBuilder<AcmeAccountKey> for DefaultBuilder {
fn create( fn create(
_resource: &AcmeAccountKey, _resource: &AcmeAccountKey,
target: &<AcmeAccountKey as Resource>::Artifact, target: &<AcmeAccountKey as Resource>::Artifact,
user_name: <Self::Prerequisites as ToArtifact>::Artifact,
(user_name, _): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Implementation { ) -> Self::Implementation {
( (
KeySymbol::new(StdCommandRunner, target.clone_rc()), KeySymbol::new(StdCommandRunner, target.clone_rc()),
@ -781,7 +781,7 @@ impl<D: Clone> ImplementationBuilder<Cron<D>> for DefaultBuilder {
UserForDomain(resource.0.clone()) UserForDomain(resource.0.clone())
} }
type Implementation = CronSymbol<'static, Box<str>, Rc<str>, StdCommandRunner>;
type Implementation = CronSymbol<'static, Box<[u8]>, Rc<str>, StdCommandRunner>;
fn create( fn create(
resource: &Cron<D>, resource: &Cron<D>,
(): &<Cron<D> as Resource>::Artifact, (): &<Cron<D> as Resource>::Artifact,

43
src/command_runner.rs

@ -1,4 +1,6 @@
use async_trait::async_trait; use async_trait::async_trait;
#[cfg(test)]
use mockall::mock;
use std::error::Error; use std::error::Error;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::io::Result as IoResult; use std::io::Result as IoResult;
@ -33,30 +35,57 @@ pub fn get_output(output: Output) -> Result<Vec<u8>, Box<dyn Error>> {
#[async_trait(?Send)] #[async_trait(?Send)]
pub trait CommandRunner { pub trait CommandRunner {
async fn run(&self, program: &str, args: &[&OsStr], stdin: &[u8]) -> IoResult<Output>;
async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult<Output>;
async fn run_with_args(&self, program: &str, args: &[&OsStr]) -> IoResult<Output> {
async fn run_with_args<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> IoResult<Output> {
self.run(program, args, b"").await self.run(program, args, b"").await
} }
async fn get_output(&self, program: &str, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
async fn get_output<'a>(
&self,
program: &str,
args: &'a [&'a OsStr],
) -> Result<Vec<u8>, Box<dyn Error>> {
let output = self.run_with_args(program, args).await?; let output = self.run_with_args(program, args).await?;
get_output(output) get_output(output)
} }
async fn run_successfully(&self, program: &str, args: &[&OsStr]) -> Result<(), Box<dyn Error>> {
async fn run_successfully<'a>(
&self,
program: &str,
args: &'a [&'a OsStr],
) -> Result<(), Box<dyn Error>> {
is_success(self.run(program, args, b"").await)?; is_success(self.run(program, args, b"").await)?;
Ok(()) Ok(())
} }
async fn get_stderr(&self, program: &str, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
async fn get_stderr<'a>(
&self,
program: &str,
args: &'a [&'a OsStr],
) -> Result<Vec<u8>, Box<dyn Error>> {
Ok(is_success(self.run_with_args(program, args).await)?.stderr) Ok(is_success(self.run_with_args(program, args).await)?.stderr)
} }
} }
#[cfg(test)]
mock! {
pub CommandRunner {
}
#[async_trait(?Send)]
impl CommandRunner for CommandRunner {
async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult<Output>;
async fn run_with_args<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> IoResult<Output>;
async fn get_output<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result<Vec<u8>, Box<dyn Error>>;
async fn run_successfully<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result<(), Box<dyn Error>>;
async fn get_stderr<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result<Vec<u8>, Box<dyn Error>>;
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct StdCommandRunner; pub struct StdCommandRunner;
#[async_trait(?Send)] #[async_trait(?Send)]
impl CommandRunner for StdCommandRunner { impl CommandRunner for StdCommandRunner {
async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult<Output> {
async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult<Output> {
//println!("{} {:?}", program, args); //println!("{} {:?}", program, args);
let mut child = Command::new(program) let mut child = Command::new(program)
.args(args) .args(args)
@ -114,7 +143,7 @@ impl Drop for TempSetEnv<'_> {
#[async_trait(?Send)] #[async_trait(?Send)]
impl<U: AsRef<str>> CommandRunner for SetuidCommandRunner<U> { impl<U: AsRef<str>> CommandRunner for SetuidCommandRunner<U> {
async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult<Output> {
async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult<Output> {
let uid = get_user_by_name(self.user_name.as_ref()) let uid = get_user_by_name(self.user_name.as_ref())
.expect("User does not exist") .expect("User does not exist")
.uid(); .uid();

12
src/locator.rs

@ -202,8 +202,9 @@ impl<P: Policy> ResourceLocator<AcmeAccountKey> for DefaultLocator<P> {
impl<P: Policy> ResourceLocator<AcmeUser> for DefaultLocator<P> { impl<P: Policy> ResourceLocator<AcmeUser> for DefaultLocator<P> {
type Prerequisites = (); type Prerequisites = ();
fn locate(_resource: &AcmeUser) -> (<AcmeUser as Resource>::Artifact, Self::Prerequisites) { fn locate(_resource: &AcmeUser) -> (<AcmeUser as Resource>::Artifact, Self::Prerequisites) {
let acme_user = P::acme_user();
(UserNameArtifact(acme_user.into()), ())
let user_name = P::acme_user();
let home = P::user_home(&user_name);
((UserNameArtifact(user_name.into()), PathArtifact::from(home)), ())
} }
} }
@ -264,10 +265,11 @@ impl<P: Policy, D: AsRef<str>> ResourceLocator<UserForDomain<D>> for DefaultLoca
} }
} }
impl<P> ResourceLocator<User> for DefaultLocator<P> {
impl<P: Policy> ResourceLocator<User> for DefaultLocator<P> {
type Prerequisites = (); type Prerequisites = ();
fn locate(_resource: &User) -> (<User as Resource>::Artifact, Self::Prerequisites) {
((), ())
fn locate(resource: &User) -> (<User as Resource>::Artifact, Self::Prerequisites) {
let home = P::user_home(&resource.0);
((PathArtifact::from(home)), ())
} }
} }

4
src/resources/mod.rs

@ -91,7 +91,7 @@ impl Resource for AcmeAccountKey {
#[derive(Debug, Hash, PartialEq, Eq)] #[derive(Debug, Hash, PartialEq, Eq)]
pub struct AcmeUser; pub struct AcmeUser;
impl Resource for AcmeUser { impl Resource for AcmeUser {
type Artifact = UserNameArtifact;
type Artifact = (UserNameArtifact, PathArtifact);
} }
#[derive(Debug, Hash, PartialEq, Eq)] #[derive(Debug, Hash, PartialEq, Eq)]
@ -138,7 +138,7 @@ pub fn get_saved_directory(
#[derive(Debug, Hash, PartialEq, Eq)] #[derive(Debug, Hash, PartialEq, Eq)]
pub struct User(pub Rc<str>); pub struct User(pub Rc<str>);
impl Resource for User { impl Resource for User {
type Artifact = ();
type Artifact = PathArtifact;
} }
#[derive(Debug, Hash, PartialEq, Eq)] #[derive(Debug, Hash, PartialEq, Eq)]

117
src/setup/cache.rs

@ -1,5 +1,4 @@
use super::{Add, AddResult, AddableResource}; use super::{Add, AddResult, AddableResource};
use crate::async_utils::sleep;
use crate::resources::{FromArtifact, FromResource}; use crate::resources::{FromArtifact, FromResource};
use async_trait::async_trait; use async_trait::async_trait;
use futures_util::future::{FutureExt, Shared}; use futures_util::future::{FutureExt, Shared};
@ -11,9 +10,10 @@ use std::future::Future;
use std::hash::Hash; use std::hash::Hash;
use std::pin::Pin; use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration;
type ResourceCache<Rs, As> = HashMap<Rs, Shared<Pin<Box<dyn Future<Output = (As, bool)>>>>>;
// FIXME: Switch Error to Rc
type ResourceCache<Rs, As> =
HashMap<Rs, Shared<Pin<Box<dyn Future<Output = Result<(As, bool), String>>>>>>;
#[derive(Debug)] #[derive(Debug)]
pub struct Cache<I, Rs, As> { pub struct Cache<I, Rs, As> {
@ -42,19 +42,17 @@ where
async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R> { async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R> {
let (storable_resource, weak_resource) = Rs::from_resource(&resource); let (storable_resource, weak_resource) = Rs::from_resource(&resource);
let mut resources = self.resources.borrow_mut(); let mut resources = self.resources.borrow_mut();
let result = if let Some(future) = resources.remove(&storable_resource) {
let future = if let Some(future) = resources.get(&storable_resource) {
assert!( assert!(
!force_run, !force_run,
"Forcing to run an already-added resource is a logical error" "Forcing to run an already-added resource is a logical error"
); );
resources.insert(storable_resource, future.clone());
drop(resources);
trace!(logger, "Resource already added"); trace!(logger, "Resource already added");
Ok(future.await)
future.clone()
} else { } else {
let inner_weak = Rc::downgrade(&self.inner); let inner_weak = Rc::downgrade(&self.inner);
let logger_weak = Rc::downgrade(logger); let logger_weak = Rc::downgrade(logger);
let future = Box::pin(async move {
let future = (Box::pin(async move {
let inner = inner_weak.upgrade().expect("Dangling!"); let inner = inner_weak.upgrade().expect("Dangling!");
let logger = logger_weak.upgrade().expect("Dangling!"); let logger = logger_weak.upgrade().expect("Dangling!");
let resource = weak_resource.upgrade().expect("Dangling!"); let resource = weak_resource.upgrade().expect("Dangling!");
@ -64,25 +62,92 @@ where
result result
.map(|(t, did_run)| (As::from_artifact(t), did_run)) .map(|(t, did_run)| (As::from_artifact(t), did_run))
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
})
}) as Pin<Box<dyn Future<Output = Result<(As, bool), String>>>>)
.shared(); .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.unwrap()
}) as Pin<Box<dyn Future<Output = (As, bool)>>>)
.shared(),
);
drop(resources);
let result = future.await;
result.map_err(std::convert::Into::into)
resources.insert(storable_resource, future.clone());
future
}; };
result.map(|(t, did_run)| (t.into_artifact(), did_run))
drop(resources);
future
.await
.map(|(t, did_run)| (t.into_artifact(), did_run))
.map_err(std::convert::Into::into)
}
}
#[cfg(test)]
mod test {
use super::{Add, AddResult, Cache, Logger};
use crate::async_utils::{join, run};
use crate::resources::{FromArtifact, FromResource, Resource};
use async_trait::async_trait;
use std::fmt::Debug;
use std::rc::{Rc, Weak};
#[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(
inner: &Rc<TestResource<&'static str>>,
) -> (Self, Weak<TestResource<&'static str>>) {
(Self::A(Rc::clone(&inner)), Rc::downgrade(&inner))
}
}
impl FromResource<TestResource<()>> for Resources {
fn from_resource(inner: &Rc<TestResource<()>>) -> (Self, Weak<TestResource<()>>) {
(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 Inner;
#[async_trait(?Send)]
impl<T: Debug + 'static> Add<TestResource<T>> for Inner {
async fn add(
&self,
logger: &Rc<Logger>,
resource: Rc<TestResource<T>>,
force_run: bool,
) -> AddResult<TestResource<T>> {
Ok(((), resource.0 != "reached"))
}
}
#[test]
fn test() {
let log = Rc::new(slog::Logger::root(slog::Discard, slog::o!()));
let cache: Cache<_, Resources, Artifacts> = Cache::new(Inner);
run(async {
let reached = cache.add(&log, Rc::new(TestResource("reached", ())), false);
let a = cache.add(&log, Rc::new(TestResource("a", ())), false);
let b = cache.add(&log, Rc::new(TestResource("b", ())), false);
let (reached, a, b) = join!(reached, a, b);
assert_eq!(((), false), reached.unwrap());
assert_eq!(((), true), a.unwrap());
assert_eq!(((), true), b.unwrap());
})
} }
} }

149
src/setup/mod.rs

@ -191,19 +191,38 @@ mod test {
#[derive(Debug, Hash, PartialEq, Eq)] #[derive(Debug, Hash, PartialEq, Eq)]
enum Resources { enum Resources {
A(Rc<TestResource<&'static str>>),
B(Rc<TestResource<()>>),
A(Rc<TestResource<()>>),
B(Rc<TestResource<&'static str>>),
C(Rc<TestResource<(&'static str, &'static str)>>),
D(Rc<TestResource<((&'static str, &'static str), &'static str)>>),
}
impl FromResource<TestResource<()>> for Resources {
fn from_resource(inner: &Rc<TestResource<()>>) -> (Self, Weak<TestResource<()>>) {
(Self::A(Rc::clone(&inner)), Rc::downgrade(&inner))
}
} }
impl FromResource<TestResource<&'static str>> for Resources { impl FromResource<TestResource<&'static str>> for Resources {
fn from_resource( fn from_resource(
inner: &Rc<TestResource<&'static str>>, inner: &Rc<TestResource<&'static str>>,
) -> (Self, Weak<TestResource<&'static str>>) { ) -> (Self, Weak<TestResource<&'static str>>) {
(Self::A(Rc::clone(&inner)), Rc::downgrade(&inner))
(Self::B(Rc::clone(&inner)), Rc::downgrade(&inner))
} }
} }
impl FromResource<TestResource<()>> for Resources {
fn from_resource(inner: &Rc<TestResource<()>>) -> (Self, Weak<TestResource<()>>) {
(Self::B(Rc::clone(&inner)), Rc::downgrade(&inner))
impl FromResource<TestResource<(&'static str, &'static str)>> for Resources {
fn from_resource(
inner: &Rc<TestResource<(&'static str, &'static str)>>,
) -> (Self, Weak<TestResource<(&'static str, &'static str)>>) {
(Self::C(Rc::clone(&inner)), Rc::downgrade(&inner))
}
}
impl FromResource<TestResource<((&'static str, &'static str), &'static str)>> for Resources {
fn from_resource(
inner: &Rc<TestResource<((&'static str, &'static str), &'static str)>>,
) -> (
Self,
Weak<TestResource<((&'static str, &'static str), &'static str)>>,
) {
(Self::D(Rc::clone(&inner)), Rc::downgrade(&inner))
} }
} }
@ -228,7 +247,78 @@ mod test {
} }
} }
#[derive(Debug)]
struct TestSymbol {
result: Result<bool, Box<str>>,
}
impl TestSymbol {
fn new(def: &str) -> Self {
let first_char = def.chars().next().unwrap();
Self {
result: if first_char == '!' {
Err(def.into())
} else {
Ok(first_char.is_uppercase())
},
}
}
}
#[async_trait(?Send)]
impl Symbol for TestSymbol {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
self.result.clone().map_err(|s| s.to_string().into())
}
async fn execute(&self) -> Result<(), Box<dyn Error>> {
Ok(())
}
}
struct TestImplementationBuilder; struct TestImplementationBuilder;
impl ImplementationBuilder<TestResource<((&'static str, &'static str), &'static str)>>
for TestImplementationBuilder
{
type Implementation = TestSymbol;
type Prerequisites = (TestResource<(&'static str, &'static str)>, TestResource<()>);
fn prerequisites(
resource: &TestResource<((&'static str, &'static str), &'static str)>,
) -> Self::Prerequisites {
(
TestResource("complex_resource", (resource.1).0), // FIXME: Only one of these can exist
TestResource((resource.1).1, ()),
)
}
fn create(
resource: &TestResource<((&'static str, &'static str), &'static str)>,
(): &(),
_inputs: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Implementation {
TestSymbol::new(resource.0)
}
}
impl ImplementationBuilder<TestResource<(&'static str, &'static str)>>
for TestImplementationBuilder
{
type Implementation = TestSymbol;
type Prerequisites = (TestResource<()>, TestResource<()>);
fn prerequisites(resource: &TestResource<(&'static str, &'static str)>) -> Self::Prerequisites {
(
TestResource((resource.1).0, ()),
TestResource((resource.1).1, ()),
)
}
fn create(
resource: &TestResource<(&'static str, &'static str)>,
(): &(),
_inputs: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Implementation {
TestSymbol::new(resource.0)
}
}
impl ImplementationBuilder<TestResource<&'static str>> for TestImplementationBuilder { impl ImplementationBuilder<TestResource<&'static str>> for TestImplementationBuilder {
type Implementation = TestSymbol; type Implementation = TestSymbol;
type Prerequisites = TestResource<()>; type Prerequisites = TestResource<()>;
@ -241,35 +331,17 @@ mod test {
(): &(), (): &(),
_inputs: <Self::Prerequisites as ToArtifact>::Artifact, _inputs: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Implementation { ) -> Self::Implementation {
TestSymbol {
reached: resource.0.chars().next().unwrap().is_uppercase(),
}
TestSymbol::new(resource.0)
} }
} }
impl ImplementationBuilder<TestResource<()>> for TestImplementationBuilder { impl ImplementationBuilder<TestResource<()>> for TestImplementationBuilder {
type Implementation = TestSymbol; type Implementation = TestSymbol;
type Prerequisites = (); type Prerequisites = ();
fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {} fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {}
fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Implementation { 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(())
TestSymbol::new(resource.0)
} }
} }
@ -306,12 +378,28 @@ mod test {
}); });
} }
#[test]
fn failing_dependencies_deadlock() {
run(async {
let (count, setup, _) = get_setup();
assert_eq!(
setup
.add(TestResource("a", (("b", "!x"), "!x")))
.await
.unwrap_err()
.to_string(),
"!x"
);
assert_eq!(*count.borrow(), 1);
});
}
#[test] #[test]
fn run_reached_symbol() { fn run_reached_symbol() {
run(async { run(async {
let (count, setup, log) = get_setup(); let (count, setup, log) = get_setup();
let did_run = setup let did_run = setup
.run_symbol(TestSymbol { reached: true }, false)
.run_symbol(TestSymbol { result: Ok(true) }, false)
.await .await
.unwrap(); .unwrap();
drop(setup); drop(setup);
@ -326,7 +414,7 @@ mod test {
run(async { run(async {
let (count, setup, log) = get_setup(); let (count, setup, log) = get_setup();
let did_run = setup let did_run = setup
.run_symbol(TestSymbol { reached: false }, false)
.run_symbol(TestSymbol { result: Ok(false) }, false)
.await .await
.unwrap(); .unwrap();
drop(setup); drop(setup);
@ -335,7 +423,8 @@ mod test {
let log = log.release(); let log = log.release();
assert_eq!(log.len(), 1); assert_eq!(log.len(), 1);
assert_eq!(log[0].0, 3); assert_eq!(log[0].0, 3);
let re = Regex::new(r"^symbol: TestSymbol \{ reached: false \}\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run\n$").unwrap();
let re = Regex::new(r"^symbol: TestSymbol \{ result: Ok\(false\) \}\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run\n$").unwrap();
assert!(re.is_match(&log[0].1)); assert!(re.is_match(&log[0].1));
}); });
} }

4
src/symbols/cron.rs

@ -10,11 +10,11 @@ pub struct Cron<'r, C, U, R> {
command_runner: &'r R, command_runner: &'r R,
} }
impl<'r, U, R> Cron<'r, Box<str>, U, R> {
impl<'r, U, R> Cron<'r, Box<[u8]>, U, R> {
pub fn new<C: AsRef<str>>(user: U, content: C, command_runner: &'r R) -> Self { pub fn new<C: AsRef<str>>(user: U, content: C, command_runner: &'r R) -> Self {
Self { Self {
user, user,
content: (String::from(content.as_ref()) + "\n").into(),
content: (String::from(content.as_ref()) + "\n").as_bytes().into(),
command_runner, command_runner,
} }
} }

73
src/symbols/git/checkout.rs

@ -92,63 +92,30 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S: AsRef<str>, B: AsRef<s
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::Checkout;
use super::{Checkout, Symbol};
use crate::async_utils::run; use crate::async_utils::run;
use crate::async_utils::sleep;
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::cell::RefCell;
use std::ffi::{OsStr, OsString};
use std::io::Result as IoResult;
use std::os::unix::process::ExitStatusExt;
use std::process::{ExitStatus, Output};
use std::time::{Duration, Instant};
struct DummyCommandRunner {
pub args: RefCell<Vec<Vec<OsString>>>,
}
#[async_trait(?Send)]
impl CommandRunner for DummyCommandRunner {
async fn run(&self, program: &str, args: &[&OsStr], stdin: &[u8]) -> IoResult<Output> {
assert_eq!(program, "git");
assert_eq!(stdin, b"");
sleep(Duration::from_millis(50)).await;
self
.args
.borrow_mut()
.push(args.iter().map(|a| a.to_os_string()).collect());
Ok(Output {
status: ExitStatus::from_raw(0),
stdout: vec![],
stderr: vec![],
})
}
}
use crate::command_runner::MockCommandRunner;
use mockall::Sequence;
#[test] #[test]
fn test() { fn test() {
let c = DummyCommandRunner {
args: RefCell::new(vec![]),
};
let checkout: Checkout<DummyCommandRunner, _, _, _, _> =
Checkout::new(".", "source", "branch", &c);
let start = Instant::now();
let mut seq = Sequence::new();
let mut c = MockCommandRunner::new();
c.expect_get_output()
.times(1)
.withf(|cmd, args| cmd == "git" && args == ["-C", ".", "rev-list", "-1", "HEAD"])
.returning(|_, _| Ok("".into()));
c.expect_get_output()
.times(1)
.in_sequence(&mut seq)
.withf(|cmd, args| cmd == "git" && args == ["-C", ".", "fetch", "source", "branch"])
.returning(|_, _| Ok("".into()));
c.expect_get_output()
.times(1)
.in_sequence(&mut seq)
.withf(|cmd, args| cmd == "git" && args == ["-C", ".", "rev-list", "-1", "FETCH_HEAD"])
.returning(|_, _| Ok("".into()));
let checkout = Checkout::new(".", "source", "branch", c);
assert!(run(checkout.target_reached()).unwrap()); assert!(run(checkout.target_reached()).unwrap());
let end = Instant::now();
let mut args = c.args.into_inner();
let first_two_args = &mut args[0..2];
first_two_args.sort_unstable();
assert_eq!(
first_two_args,
[
["-C", ".", "fetch", "source", "branch"],
["-C", ".", "rev-list", "-1", "HEAD"],
]
);
assert_eq!(args[2], ["-C", ".", "rev-list", "-1", "FETCH_HEAD"]);
assert!((end - start).as_millis() >= 100);
assert!((end - start).as_millis() < 150);
} }
} }

81
src/symbols/tls/csr.rs

@ -2,6 +2,7 @@ use crate::command_runner::CommandRunner;
use crate::symbols::Symbol; use crate::symbols::Symbol;
use async_trait::async_trait; use async_trait::async_trait;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::convert::AsRef;
use std::error::Error; use std::error::Error;
use std::path::Path; use std::path::Path;
@ -25,11 +26,9 @@ impl<C, D, K, P> Csr<C, D, K, P> {
} }
#[async_trait(?Send)] #[async_trait(?Send)]
impl<C: CommandRunner, D: Borrow<str>, K: Borrow<Path>, P: Borrow<Path>> Symbol
for Csr<C, D, K, P>
{
impl<C: CommandRunner, D: Borrow<str>, K: AsRef<Path>, P: AsRef<Path>> Symbol for Csr<C, D, K, P> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> { async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.csr_path.borrow().exists() {
if !self.csr_path.as_ref().exists() {
return Ok(false); return Ok(false);
} }
@ -37,10 +36,10 @@ impl<C: CommandRunner, D: Borrow<str>, K: Borrow<Path>, P: Borrow<Path>> Symbol
.command_runner .command_runner
.get_stderr( .get_stderr(
"openssl", "openssl",
args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",],
args!["req", "-in", self.csr_path.as_ref(), "-noout", "-verify",],
) )
.await?; .await?;
Ok(output == b"verify OK\n")
Ok(output == b"verify OK\n" || output == b"Certificate request self-signature verify OK\n")
} }
async fn execute(&self) -> Result<(), Box<dyn Error>> { async fn execute(&self) -> Result<(), Box<dyn Error>> {
@ -53,9 +52,9 @@ impl<C: CommandRunner, D: Borrow<str>, K: Borrow<Path>, P: Borrow<Path>> Symbol
"-new", "-new",
"-sha256", "-sha256",
"-key", "-key",
self.key_path.borrow(),
self.key_path.as_ref(),
"-out", "-out",
self.csr_path.borrow(),
self.csr_path.as_ref(),
"-subj", "-subj",
format!("/CN={}", self.domain.borrow()), format!("/CN={}", self.domain.borrow()),
], ],
@ -65,4 +64,68 @@ impl<C: CommandRunner, D: Borrow<str>, K: Borrow<Path>, P: Borrow<Path>> Symbol
} }
#[cfg(test)] #[cfg(test)]
mod test {}
mod test {
use super::{Csr, Symbol};
use crate::async_utils::run;
use crate::command_runner::MockCommandRunner;
#[test]
fn test_bookworm_success() {
let mut command_runner = MockCommandRunner::new();
command_runner
.expect_get_stderr()
.times(1)
.returning(|_, _| Ok("Certificate request self-signature verify OK\n".into()));
let symbol = Csr::new(command_runner, "", "/nonexisting", "/"); // FIXME: Csr path has to be an existing file
run(async {
assert_eq!(symbol.target_reached().await.unwrap(), true);
});
}
#[test]
fn test_bookworm_invalid() {
let mut command_runner = MockCommandRunner::new();
command_runner
.expect_get_stderr()
.times(1)
.returning(|_, _| {
Ok("Unable to load X509 request
40F746B61E7F0000:error:0480006C:PEM routines:get_name:no start line:../crypto/pem/pem_lib.c:763:Expecting: CERTIFICATE REQUEST
" .into())
});
let symbol = Csr::new(command_runner, "", "/nonexisting", "/"); // FIXME: Csr path has to be an existing file
run(async {
assert_eq!(symbol.target_reached().await.unwrap(), false);
});
}
#[test]
fn test_bullseye_success() {
let mut command_runner = MockCommandRunner::new();
command_runner
.expect_get_stderr()
.times(1)
.returning(|_, _| Ok("verify OK\n".into()));
let symbol = Csr::new(command_runner, "", "/nonexisting", "/"); // FIXME: Csr path has to be an existing file
run(async {
assert_eq!(symbol.target_reached().await.unwrap(), true);
});
}
#[test]
fn test_bullseye_invalid() {
let mut command_runner = MockCommandRunner::new();
command_runner
.expect_get_stderr()
.times(1)
.returning(|_, _| {
Ok("unable to load X509 request
140032085857600:error:0909006C:PEM routines:get_name:no start line:../crypto/pem/pem_lib.c:745:Expecting: CERTIFICATE REQUEST
" .into())
});
let symbol = Csr::new(command_runner, "", "/nonexisting", "/"); // FIXME: Csr path has to be an existing file
run(async {
assert_eq!(symbol.target_reached().await.unwrap(), false);
});
}
}

218
src/symbols/tls/key.rs

@ -30,7 +30,7 @@ impl<C: CommandRunner, P: AsRef<Path>> Symbol for Key<C, P> {
return Ok(false); return Ok(false);
} }
let stdout = self
let mut stdout = self
.command_runner .command_runner
.get_output( .get_output(
"openssl", "openssl",
@ -44,9 +44,12 @@ impl<C: CommandRunner, P: AsRef<Path>> Symbol for Key<C, P> {
], ],
) )
.await?; .await?;
if stdout.starts_with("RSA ".as_ref()) {
stdout = stdout.split_off(4);
}
Ok( Ok(
stdout.ends_with(b"RSA key ok\n") stdout.ends_with(b"RSA key ok\n")
&& stdout.starts_with(format!("RSA Private-Key: ({} bit, 2 primes)\n", self.bits).as_ref()),
&& stdout.starts_with(format!("Private-Key: ({} bit, 2 primes)\n", self.bits).as_ref()),
) )
} }
@ -67,4 +70,213 @@ impl<C: CommandRunner, P: AsRef<Path>> Symbol for Key<C, P> {
} }
#[cfg(test)] #[cfg(test)]
mod test {}
mod test {
use super::{Key, Symbol};
use crate::async_utils::run;
use crate::command_runner::MockCommandRunner;
#[test]
fn test_bookworm_success() {
let mut command_runner = MockCommandRunner::new();
command_runner
.expect_get_output()
.times(1)
.returning(|_, _| {
Ok(
"Private-Key: (4096 bit, 2 primes)
modulus:
00:...
publicExponent: 65537 (0x10001)
privateExponent:
57:...
prime1:
00:...
prime2:
00:...
exponent1:
2b:...
exponent2:
0e:...
coefficient:
43:...
RSA key ok
"
.into(),
)
});
let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file
run(async {
assert_eq!(symbol.target_reached().await.unwrap(), true);
});
}
#[test]
fn test_bookworm_short_key() {
let mut command_runner = MockCommandRunner::new();
command_runner
.expect_get_output()
.times(1)
.returning(|_, _| {
Ok(
"Private-Key: (2048 bit, 2 primes)
modulus:
00:...
publicExponent: 65537 (0x10001)
privateExponent:
57:...
prime1:
00:...
prime2:
00:...
exponent1:
2b:...
exponent2:
0e:...
coefficient:
43:...
RSA key ok
"
.into(),
)
});
let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file
run(async {
assert_eq!(symbol.target_reached().await.unwrap(), false);
});
}
#[test]
fn test_bookworm_broken_key() {
let mut command_runner = MockCommandRunner::new();
command_runner.expect_get_output()
.times(1)
.returning(|_, _| Ok("Private-Key: (4096 bit, 2 primes)
modulus:
00:...
publicExponent: 65537 (0x10001)
privateExponent:
57:...
prime1:
00:...
prime2:
00:...
exponent1:
2b:...
exponent2:
0e:...
coefficient:
43:...
RSA key not ok
40C782E5E77F0000:error:0200007E:rsa routines:rsa_validate_keypair_multiprime:iqmp not inverse of q:../crypto/rsa/rsa_chk.c:196:
".into()));
let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file
run(async {
assert_eq!(symbol.target_reached().await.unwrap(), false);
});
}
#[test]
fn test_bullseye_success() {
let mut command_runner = MockCommandRunner::new();
command_runner
.expect_get_output()
.times(1)
.returning(|_, _| {
Ok(
"RSA Private-Key: (4096 bit, 2 primes)
modulus:
00:...
publicExponent: 65537 (0x10001)
privateExponent:
57:...
prime1:
00:...
prime2:
00:...
exponent1:
2b:...
exponent2:
0e:...
coefficient:
43:...
RSA key ok
"
.into(),
)
});
let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file
run(async {
assert_eq!(symbol.target_reached().await.unwrap(), true);
});
}
#[test]
fn test_bullseye_short_key() {
let mut command_runner = MockCommandRunner::new();
command_runner
.expect_get_output()
.times(1)
.returning(|_, _| {
Ok(
"RSA Private-Key: (2048 bit, 2 primes)
modulus:
00:...
publicExponent: 65537 (0x10001)
privateExponent:
57:...
prime1:
00:...
prime2:
00:...
exponent1:
2b:...
exponent2:
0e:...
coefficient:
43:...
RSA key ok
"
.into(),
)
});
let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file
run(async {
assert_eq!(symbol.target_reached().await.unwrap(), false);
});
}
#[test]
fn test_bullseye_broken_key() {
let mut command_runner = MockCommandRunner::new();
command_runner
.expect_get_output()
.times(1)
.returning(|_, _| {
Ok(
"RSA Private-Key: (4096 bit, 2 primes)
modulus:
00:...
publicExponent: 65537 (0x10001)
privateExponent:
57:...
prime1:
00:...
prime2:
00:...
exponent1:
2b:...
exponent2:
0e:...
coefficient:
43:...
RSA key error: iqmp not inverse of q
"
.into(),
)
});
let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file
run(async {
assert_eq!(symbol.target_reached().await.unwrap(), false);
});
}
}

13
src/symbols/user.rs

@ -4,27 +4,30 @@ use async_trait::async_trait;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::error::Error; use std::error::Error;
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
use std::path::Path;
pub type Wait = Lazy<Semaphore>; pub type Wait = Lazy<Semaphore>;
static WAIT: Wait = Lazy::new(|| Semaphore::new(1)); static WAIT: Wait = Lazy::new(|| Semaphore::new(1));
#[derive(Debug)] #[derive(Debug)]
pub struct User<U, C> {
pub struct User<U, H, C> {
user_name: U, user_name: U,
home_path: H,
command_runner: C, command_runner: C,
} }
impl<U, C> User<U, C> {
pub const fn new(user_name: U, command_runner: C) -> Self {
impl<U, H, C> User<U, H, C> {
pub const fn new(user_name: U, home_path: H, command_runner: C) -> Self {
Self { Self {
user_name, user_name,
home_path,
command_runner, command_runner,
} }
} }
} }
#[async_trait(?Send)] #[async_trait(?Send)]
impl<U: AsRef<str>, C: CommandRunner> Symbol for User<U, C> {
impl<U: AsRef<str>, H: AsRef<Path>, C: CommandRunner> Symbol for User<U, H, C> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> { async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let output = self let output = self
.command_runner .command_runner
@ -48,6 +51,8 @@ impl<U: AsRef<str>, C: CommandRunner> Symbol for User<U, C> {
args![ args![
// "-m", // Necessary for Fedora, not accepted in Debian // "-m", // Necessary for Fedora, not accepted in Debian
"--system", "--system",
"--home",
self.home_path.as_ref(),
self.user_name.as_ref(), self.user_name.as_ref(),
], ],
) )

Loading…
Cancel
Save