Compare commits
6 commits
d091265d27
...
e40e65bd62
| Author | SHA1 | Date | |
|---|---|---|---|
| e40e65bd62 | |||
| 6b34c9ea34 | |||
| 6d564e58e0 | |||
| c41ad54c37 | |||
| 988a3b0fe7 | |||
| 93dba46387 |
12 changed files with 588 additions and 155 deletions
|
|
@ -19,3 +19,4 @@ nonzero_ext = "0.3.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
mockall = "0.11.4"
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
let user_name = P::acme_user();
|
||||||
(UserNameArtifact(acme_user.into()), ())
|
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)), ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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, future.clone());
|
||||||
resources.insert(
|
future
|
||||||
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)
|
|
||||||
};
|
};
|
||||||
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
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>>),
|
A(Rc<TestResource<()>>),
|
||||||
B(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 {
|
impl FromResource<TestResource<(&'static str, &'static str)>> for Resources {
|
||||||
fn from_resource(inner: &Rc<TestResource<()>>) -> (Self, Weak<TestResource<()>>) {
|
fn from_resource(
|
||||||
(Self::B(Rc::clone(&inner)), Rc::downgrade(&inner))
|
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 {
|
TestSymbol::new(resource.0)
|
||||||
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
TestSymbol::new(resource.0)
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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::MockCommandRunner;
|
||||||
use crate::command_runner::CommandRunner;
|
use mockall::Sequence;
|
||||||
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![],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
let c = DummyCommandRunner {
|
let mut seq = Sequence::new();
|
||||||
args: RefCell::new(vec![]),
|
let mut c = MockCommandRunner::new();
|
||||||
};
|
c.expect_get_output()
|
||||||
let checkout: Checkout<DummyCommandRunner, _, _, _, _> =
|
.times(1)
|
||||||
Checkout::new(".", "source", "branch", &c);
|
.withf(|cmd, args| cmd == "git" && args == ["-C", ".", "rev-list", "-1", "HEAD"])
|
||||||
let start = Instant::now();
|
.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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
impl<C: CommandRunner, D: Borrow<str>, K: AsRef<Path>, P: AsRef<Path>> Symbol for Csr<C, D, K, P> {
|
||||||
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
impl<U, H, C> User<U, H, C> {
|
||||||
pub const fn new(user_name: U, command_runner: C) -> Self {
|
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…
Add table
Add a link
Reference in a new issue