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]
|
||||
tempfile = "3"
|
||||
mockall = "0.11.4"
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ impl<D: Clone> ImplementationBuilder<Cert<D>> for DefaultBuilder {
|
|||
fn create(
|
||||
resource: &Cert<D>,
|
||||
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 {
|
||||
CertSymbol::new(
|
||||
resource.0.clone(),
|
||||
|
|
@ -552,13 +552,13 @@ impl<D: Clone> ImplementationBuilder<UserForDomain<D>> for DefaultBuilder {
|
|||
type Prerequisites = ();
|
||||
fn prerequisites(_resource: &UserForDomain<D>) -> Self::Prerequisites {}
|
||||
|
||||
type Implementation = UserSymbol<Rc<str>, StdCommandRunner>;
|
||||
type Implementation = UserSymbol<Rc<str>, Rc<Path>, StdCommandRunner>;
|
||||
fn create(
|
||||
_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::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 = ();
|
||||
fn prerequisites(_resource: &User) -> Self::Prerequisites {}
|
||||
|
||||
type Implementation = UserSymbol<Rc<str>, StdCommandRunner>;
|
||||
type Implementation = UserSymbol<Rc<str>, Rc<Path>, StdCommandRunner>;
|
||||
fn create(
|
||||
resource: &User,
|
||||
(): &<User as Resource>::Artifact,
|
||||
home_path: &<User as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> 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 = ();
|
||||
fn prerequisites(_resource: &AcmeUser) -> Self::Prerequisites {}
|
||||
|
||||
type Implementation = UserSymbol<Rc<str>, StdCommandRunner>;
|
||||
type Implementation = UserSymbol<Rc<str>, Rc<Path>, StdCommandRunner>;
|
||||
fn create(
|
||||
_resource: &AcmeUser,
|
||||
user_name: &<AcmeUser as Resource>::Artifact,
|
||||
(user_name, home_path): &<AcmeUser as Resource>::Artifact,
|
||||
(): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> 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(
|
||||
_resource: &AcmeChallengesDir,
|
||||
target: &<AcmeChallengesDir as Resource>::Artifact,
|
||||
user_name: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
(user_name, _): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Implementation {
|
||||
(
|
||||
DirSymbol::new(target.clone_rc()),
|
||||
|
|
@ -658,7 +658,7 @@ impl ImplementationBuilder<AcmeAccountKey> for DefaultBuilder {
|
|||
fn create(
|
||||
_resource: &AcmeAccountKey,
|
||||
target: &<AcmeAccountKey as Resource>::Artifact,
|
||||
user_name: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
(user_name, _): <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Implementation {
|
||||
(
|
||||
KeySymbol::new(StdCommandRunner, target.clone_rc()),
|
||||
|
|
@ -781,7 +781,7 @@ impl<D: Clone> ImplementationBuilder<Cron<D>> for DefaultBuilder {
|
|||
UserForDomain(resource.0.clone())
|
||||
}
|
||||
|
||||
type Implementation = CronSymbol<'static, Box<str>, Rc<str>, StdCommandRunner>;
|
||||
type Implementation = CronSymbol<'static, Box<[u8]>, Rc<str>, StdCommandRunner>;
|
||||
fn create(
|
||||
resource: &Cron<D>,
|
||||
(): &<Cron<D> as Resource>::Artifact,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use async_trait::async_trait;
|
||||
#[cfg(test)]
|
||||
use mockall::mock;
|
||||
use std::error::Error;
|
||||
use std::ffi::OsStr;
|
||||
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)]
|
||||
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
|
||||
}
|
||||
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?;
|
||||
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)?;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[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)]
|
||||
pub struct StdCommandRunner;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
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);
|
||||
let mut child = Command::new(program)
|
||||
.args(args)
|
||||
|
|
@ -114,7 +143,7 @@ impl Drop for TempSetEnv<'_> {
|
|||
|
||||
#[async_trait(?Send)]
|
||||
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())
|
||||
.expect("User does not exist")
|
||||
.uid();
|
||||
|
|
|
|||
|
|
@ -202,8 +202,9 @@ impl<P: Policy> ResourceLocator<AcmeAccountKey> for DefaultLocator<P> {
|
|||
impl<P: Policy> ResourceLocator<AcmeUser> for DefaultLocator<P> {
|
||||
type 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 = ();
|
||||
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)]
|
||||
pub struct AcmeUser;
|
||||
impl Resource for AcmeUser {
|
||||
type Artifact = UserNameArtifact;
|
||||
type Artifact = (UserNameArtifact, PathArtifact);
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
|
|
@ -138,7 +138,7 @@ pub fn get_saved_directory(
|
|||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub struct User(pub Rc<str>);
|
||||
impl Resource for User {
|
||||
type Artifact = ();
|
||||
type Artifact = PathArtifact;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use super::{Add, AddResult, AddableResource};
|
||||
use crate::async_utils::sleep;
|
||||
use crate::resources::{FromArtifact, FromResource};
|
||||
use async_trait::async_trait;
|
||||
use futures_util::future::{FutureExt, Shared};
|
||||
|
|
@ -11,9 +10,10 @@ use std::future::Future;
|
|||
use std::hash::Hash;
|
||||
use std::pin::Pin;
|
||||
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)]
|
||||
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> {
|
||||
let (storable_resource, weak_resource) = Rs::from_resource(&resource);
|
||||
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!(
|
||||
!force_run,
|
||||
"Forcing to run an already-added resource is a logical error"
|
||||
);
|
||||
resources.insert(storable_resource, future.clone());
|
||||
drop(resources);
|
||||
trace!(logger, "Resource already added");
|
||||
Ok(future.await)
|
||||
future.clone()
|
||||
} else {
|
||||
let inner_weak = Rc::downgrade(&self.inner);
|
||||
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 logger = logger_weak.upgrade().expect("Dangling!");
|
||||
let resource = weak_resource.upgrade().expect("Dangling!");
|
||||
|
|
@ -64,25 +62,92 @@ where
|
|||
result
|
||||
.map(|(t, did_run)| (As::from_artifact(t), did_run))
|
||||
.map_err(|e| 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.unwrap()
|
||||
}) as Pin<Box<dyn Future<Output = (As, bool)>>>)
|
||||
.shared(),
|
||||
);
|
||||
drop(resources);
|
||||
let result = future.await;
|
||||
result.map_err(std::convert::Into::into)
|
||||
}) as Pin<Box<dyn Future<Output = Result<(As, bool), String>>>>)
|
||||
.shared();
|
||||
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
149
src/setup/mod.rs
|
|
@ -191,19 +191,38 @@ mod test {
|
|||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
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 {
|
||||
fn from_resource(
|
||||
inner: &Rc<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;
|
||||
|
||||
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 {
|
||||
type Implementation = TestSymbol;
|
||||
type Prerequisites = TestResource<()>;
|
||||
|
|
@ -241,35 +331,17 @@ mod test {
|
|||
(): &(),
|
||||
_inputs: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
) -> Self::Implementation {
|
||||
TestSymbol {
|
||||
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
||||
}
|
||||
TestSymbol::new(resource.0)
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
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]
|
||||
fn run_reached_symbol() {
|
||||
run(async {
|
||||
let (count, setup, log) = get_setup();
|
||||
let did_run = setup
|
||||
.run_symbol(TestSymbol { reached: true }, false)
|
||||
.run_symbol(TestSymbol { result: Ok(true) }, false)
|
||||
.await
|
||||
.unwrap();
|
||||
drop(setup);
|
||||
|
|
@ -326,7 +414,7 @@ mod test {
|
|||
run(async {
|
||||
let (count, setup, log) = get_setup();
|
||||
let did_run = setup
|
||||
.run_symbol(TestSymbol { reached: false }, false)
|
||||
.run_symbol(TestSymbol { result: Ok(false) }, false)
|
||||
.await
|
||||
.unwrap();
|
||||
drop(setup);
|
||||
|
|
@ -335,7 +423,8 @@ mod test {
|
|||
let log = log.release();
|
||||
assert_eq!(log.len(), 1);
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ pub struct Cron<'r, C, U, 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 {
|
||||
Self {
|
||||
user,
|
||||
content: (String::from(content.as_ref()) + "\n").into(),
|
||||
content: (String::from(content.as_ref()) + "\n").as_bytes().into(),
|
||||
command_runner,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,63 +92,30 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S: AsRef<str>, B: AsRef<s
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Checkout;
|
||||
use super::{Checkout, Symbol};
|
||||
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]
|
||||
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());
|
||||
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 async_trait::async_trait;
|
||||
use std::borrow::Borrow;
|
||||
use std::convert::AsRef;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
|
||||
|
|
@ -25,11 +26,9 @@ impl<C, D, K, P> Csr<C, D, K, P> {
|
|||
}
|
||||
|
||||
#[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>> {
|
||||
if !self.csr_path.borrow().exists() {
|
||||
if !self.csr_path.as_ref().exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
|
|
@ -37,10 +36,10 @@ impl<C: CommandRunner, D: Borrow<str>, K: Borrow<Path>, P: Borrow<Path>> Symbol
|
|||
.command_runner
|
||||
.get_stderr(
|
||||
"openssl",
|
||||
args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",],
|
||||
args!["req", "-in", self.csr_path.as_ref(), "-noout", "-verify",],
|
||||
)
|
||||
.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>> {
|
||||
|
|
@ -53,9 +52,9 @@ impl<C: CommandRunner, D: Borrow<str>, K: Borrow<Path>, P: Borrow<Path>> Symbol
|
|||
"-new",
|
||||
"-sha256",
|
||||
"-key",
|
||||
self.key_path.borrow(),
|
||||
self.key_path.as_ref(),
|
||||
"-out",
|
||||
self.csr_path.borrow(),
|
||||
self.csr_path.as_ref(),
|
||||
"-subj",
|
||||
format!("/CN={}", self.domain.borrow()),
|
||||
],
|
||||
|
|
@ -65,4 +64,68 @@ impl<C: CommandRunner, D: Borrow<str>, K: Borrow<Path>, P: Borrow<Path>> Symbol
|
|||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
let stdout = self
|
||||
let mut stdout = self
|
||||
.command_runner
|
||||
.get_output(
|
||||
"openssl",
|
||||
|
|
@ -44,9 +44,12 @@ impl<C: CommandRunner, P: AsRef<Path>> Symbol for Key<C, P> {
|
|||
],
|
||||
)
|
||||
.await?;
|
||||
if stdout.starts_with("RSA ".as_ref()) {
|
||||
stdout = stdout.split_off(4);
|
||||
}
|
||||
Ok(
|
||||
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)]
|
||||
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 std::error::Error;
|
||||
use tokio::sync::Semaphore;
|
||||
use std::path::Path;
|
||||
|
||||
pub type Wait = Lazy<Semaphore>;
|
||||
static WAIT: Wait = Lazy::new(|| Semaphore::new(1));
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct User<U, C> {
|
||||
pub struct User<U, H, C> {
|
||||
user_name: U,
|
||||
home_path: H,
|
||||
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 {
|
||||
user_name,
|
||||
home_path,
|
||||
command_runner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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>> {
|
||||
let output = self
|
||||
.command_runner
|
||||
|
|
@ -48,6 +51,8 @@ impl<U: AsRef<str>, C: CommandRunner> Symbol for User<U, C> {
|
|||
args![
|
||||
// "-m", // Necessary for Fedora, not accepted in Debian
|
||||
"--system",
|
||||
"--home",
|
||||
self.home_path.as_ref(),
|
||||
self.user_name.as_ref(),
|
||||
],
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue