Adrian Heine
9 years ago
commit
2ba4c3b1c2
10 changed files with 556 additions and 0 deletions
-
2.gitignore
-
6Cargo.toml
-
20src/command_runner.rs
-
22src/lib.rs
-
41src/loggers.rs
-
202src/schema.rs
-
8src/symbols/mod.rs
-
1src/symbols/systemd/mod.rs
-
67src/symbols/systemd/user_session.rs
-
187src/symbols/user.rs
@ -0,0 +1,2 @@ |
|||
target |
|||
Cargo.lock |
@ -0,0 +1,6 @@ |
|||
[package] |
|||
name = "schematics" |
|||
version = "0.1.0" |
|||
authors = ["Adrian Heine <mail@adrianheine.de>"] |
|||
|
|||
[dependencies] |
@ -0,0 +1,20 @@ |
|||
use std::io::Result as IoResult;
|
|||
use std::process::Command;
|
|||
use std::process::Output;
|
|||
|
|||
pub trait CommandRunner {
|
|||
fn run_with_args(&self, program: &str, args: &[&str]) -> IoResult<Output>;
|
|||
}
|
|||
|
|||
#[derive(Debug)]
|
|||
pub struct StdCommandRunner;
|
|||
|
|||
impl CommandRunner for StdCommandRunner {
|
|||
fn run_with_args(&self, program: &str, args: &[&str]) -> IoResult<Output> {
|
|||
Command::new(program).args(args).output()
|
|||
}
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
mod test {
|
|||
}
|
@ -0,0 +1,22 @@ |
|||
// rustfmt
|
|||
|
|||
#![deny(fat_ptr_transmutes,
|
|||
trivial_casts, trivial_numeric_casts, unsafe_code,
|
|||
unstable_features, unused_extern_crates,
|
|||
unused_import_braces, unused_qualifications,
|
|||
unused_results, variant_size_differences
|
|||
)]
|
|||
|
|||
/*
|
|||
#![warn(missing_docs,
|
|||
missing_copy_implementations,
|
|||
missing_debug_implementations
|
|||
)]
|
|||
*/
|
|||
|
|||
#![allow(box_pointers)]
|
|||
|
|||
pub mod command_runner;
|
|||
pub mod loggers;
|
|||
pub mod symbols;
|
|||
pub mod schema;
|
@ -0,0 +1,41 @@ |
|||
use std::io::stderr;
|
|||
use std::io::Write;
|
|||
|
|||
pub trait Logger {
|
|||
fn write(&mut self, &str);
|
|||
fn debug(&mut self, &str);
|
|||
}
|
|||
|
|||
pub struct StdErrLogger;
|
|||
|
|||
impl Logger for StdErrLogger {
|
|||
fn debug(&mut self, str: &str) {
|
|||
writeln!(&mut stderr(), "{}", str).unwrap();
|
|||
}
|
|||
fn write(&mut self, str: &str) {
|
|||
writeln!(&mut stderr(), "{}", str).unwrap();
|
|||
}
|
|||
}
|
|||
|
|||
pub struct FilteringLogger<'a> {
|
|||
logger: &'a mut Logger
|
|||
}
|
|||
|
|||
impl<'a> FilteringLogger<'a> {
|
|||
pub fn new(logger: &'a mut Logger) -> Self {
|
|||
FilteringLogger { logger: logger }
|
|||
}
|
|||
}
|
|||
|
|||
impl<'a> Logger for FilteringLogger<'a> {
|
|||
fn debug(&mut self, str: &str) {
|
|||
return
|
|||
}
|
|||
fn write(&mut self, str: &str) {
|
|||
self.logger.write(str)
|
|||
}
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
mod test {
|
|||
}
|
@ -0,0 +1,202 @@ |
|||
use std::error::Error;
|
|||
use std::fmt;
|
|||
use std::io::Write;
|
|||
|
|||
use loggers::Logger;
|
|||
use symbols::Symbol;
|
|||
|
|||
#[derive(Debug,PartialEq)]
|
|||
pub enum SymbolRunError<E: Error> {
|
|||
Symbol(E),
|
|||
ExecuteDidNotReach(())
|
|||
}
|
|||
|
|||
impl<E: Error> Error for SymbolRunError<E> {
|
|||
fn description(&self) -> &str {
|
|||
match self {
|
|||
&SymbolRunError::Symbol(_) => "Symbol execution error",
|
|||
&SymbolRunError::ExecuteDidNotReach(_) => "Target not reached after executing symbol"
|
|||
}
|
|||
}
|
|||
fn cause(&self) -> Option<&Error> {
|
|||
match self {
|
|||
&SymbolRunError::Symbol(ref e) => Some(e),
|
|||
&SymbolRunError::ExecuteDidNotReach(_) => None
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl<E: Error> fmt::Display for SymbolRunError<E> {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
match self {
|
|||
&SymbolRunError::Symbol(ref e) => write!(f, "{}", e),
|
|||
&SymbolRunError::ExecuteDidNotReach(_) => write!(f, "{}", self.description())
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl<E: Error> From<E> for SymbolRunError<E> {
|
|||
fn from(err: E) -> SymbolRunError<E> {
|
|||
SymbolRunError::Symbol(err)
|
|||
}
|
|||
}
|
|||
|
|||
pub trait SymbolRunner {
|
|||
fn run_symbol<S: Symbol + fmt::Display>(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError<S::Error>>
|
|||
where S::Error: Error;
|
|||
}
|
|||
|
|||
pub struct InitializingSymbolRunner;
|
|||
|
|||
impl SymbolRunner for InitializingSymbolRunner {
|
|||
fn run_symbol<S: Symbol + fmt::Display>(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError<S::Error>>
|
|||
where S::Error: Error
|
|||
{
|
|||
let target_reached = try!(symbol.target_reached());
|
|||
if target_reached {
|
|||
logger.write(format!("{} already reached", symbol).as_str());
|
|||
} else {
|
|||
logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str());
|
|||
logger.write(format!("Executing {}", symbol).as_str());
|
|||
try!(symbol.execute());
|
|||
let target_reached = try!(symbol.target_reached());
|
|||
logger.debug(format!("Symbol reports target_reached: {:?} (should be true)", target_reached).as_str());
|
|||
if !target_reached {
|
|||
return Err(SymbolRunError::ExecuteDidNotReach(()));
|
|||
}
|
|||
}
|
|||
Ok(())
|
|||
}
|
|||
}
|
|||
|
|||
pub struct DrySymbolRunner;
|
|||
|
|||
impl SymbolRunner for DrySymbolRunner {
|
|||
fn run_symbol<S: Symbol + fmt::Display>(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError<S::Error>>
|
|||
where S::Error: Error
|
|||
{
|
|||
let target_reached = try!(symbol.target_reached());
|
|||
logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str());
|
|||
if !target_reached {
|
|||
logger.write(format!("Would execute {}", symbol).as_str());
|
|||
}
|
|||
Ok(())
|
|||
}
|
|||
}
|
|||
|
|||
// FIXME: Add ExpectingSymbolRunner
|
|||
|
|||
#[cfg(test)]
|
|||
mod test {
|
|||
use std::cell::RefCell;
|
|||
use std::error::Error;
|
|||
use std::fmt;
|
|||
|
|||
use loggers::Logger;
|
|||
use symbols::Symbol;
|
|||
use schema::SymbolRunner;
|
|||
use schema::InitializingSymbolRunner;
|
|||
use schema::SymbolRunError;
|
|||
|
|||
#[derive(Debug, PartialEq, Clone)]
|
|||
enum DummySymbolError {
|
|||
Error(())
|
|||
}
|
|||
|
|||
impl Error for DummySymbolError {
|
|||
fn description(&self) -> &str {
|
|||
return "Description";
|
|||
}
|
|||
}
|
|||
|
|||
impl fmt::Display for DummySymbolError {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
write!(f, "Dummy symbol error")
|
|||
}
|
|||
}
|
|||
|
|||
#[derive(Debug)]
|
|||
struct DummySymbol {
|
|||
_target_reacheds: Vec<Result<bool, DummySymbolError>>,
|
|||
_cur_target_reached: RefCell<usize>,
|
|||
_execute: Result<(), DummySymbolError>,
|
|||
}
|
|||
|
|||
impl Symbol for DummySymbol {
|
|||
type Error = DummySymbolError;
|
|||
fn target_reached(&self) -> Result<bool, Self::Error> {
|
|||
let mut cur = self._cur_target_reached.borrow_mut();
|
|||
let ret = self._target_reacheds[*cur].clone();
|
|||
*cur = *cur + 1;
|
|||
ret
|
|||
}
|
|||
fn execute(&self) -> Result<(), Self::Error> { self._execute.clone() }
|
|||
}
|
|||
|
|||
impl fmt::Display for DummySymbol {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
write!(f, "Dummy symbol")
|
|||
}
|
|||
}
|
|||
|
|||
impl DummySymbol {
|
|||
fn new(target_reached: Vec<Result<bool, DummySymbolError>>,
|
|||
execute: Result<(), DummySymbolError>) -> DummySymbol {
|
|||
DummySymbol { _target_reacheds: target_reached, _cur_target_reached: RefCell::new(0), _execute: execute }
|
|||
}
|
|||
}
|
|||
|
|||
struct DummyLogger {
|
|||
log: Vec<String>
|
|||
}
|
|||
|
|||
impl DummyLogger {
|
|||
fn new() -> DummyLogger {
|
|||
DummyLogger { log: Vec::new() }
|
|||
}
|
|||
}
|
|||
|
|||
impl Logger for DummyLogger {
|
|||
fn write(&mut self, line: &str) {
|
|||
self.log.push(From::from(line));
|
|||
}
|
|||
fn debug(&mut self, line: &str) {
|
|||
self.log.push(From::from(line));
|
|||
}
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn nothing_needed_to_be_done() {
|
|||
let result: Result<(), SymbolRunError<DummySymbolError>> = InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(vec![Ok(true)], Ok(())) );
|
|||
assert_eq!(
|
|||
result,
|
|||
Ok(())
|
|||
);
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn everything_is_ok() {
|
|||
let result: Result<(), SymbolRunError<DummySymbolError>> = InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(vec![Ok(false), Ok(true)], Ok(())) );
|
|||
assert_eq!(
|
|||
result,
|
|||
Ok(())
|
|||
);
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn executing_did_not_change_state() {
|
|||
let result: Result<(), SymbolRunError<DummySymbolError>> = InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(vec![Ok(false), Ok(false)], Ok(())));
|
|||
assert_eq!(
|
|||
result,
|
|||
Err(SymbolRunError::ExecuteDidNotReach(()))
|
|||
);
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn executing_did_not_work() {
|
|||
assert_eq!(
|
|||
InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(vec![Ok(false)], Err(DummySymbolError::Error(()))) ),
|
|||
Err(SymbolRunError::Symbol(DummySymbolError::Error(())))
|
|||
);
|
|||
}
|
|||
}
|
@ -0,0 +1,8 @@ |
|||
pub trait Symbol {
|
|||
type Error;
|
|||
fn target_reached(&self) -> Result<bool, Self::Error>;
|
|||
fn execute(&self) -> Result<(), Self::Error>;
|
|||
}
|
|||
|
|||
pub mod user;
|
|||
pub mod systemd;
|
@ -0,0 +1 @@ |
|||
pub mod user_session;
|
@ -0,0 +1,67 @@ |
|||
use std::error::Error;
|
|||
use std::fmt;
|
|||
use std::io::Error as IoError;
|
|||
use std::path::PathBuf;
|
|||
|
|||
use command_runner::CommandRunner;
|
|||
use symbols::Symbol;
|
|||
|
|||
#[derive(Debug)]
|
|||
pub enum SystemdUserSessionError<E: Error> {
|
|||
ExecError(E)
|
|||
}
|
|||
|
|||
impl<E: Error> Error for SystemdUserSessionError<E> {
|
|||
fn description(&self) -> &str {
|
|||
match self {
|
|||
&SystemdUserSessionError::ExecError(ref e) => e.description()
|
|||
}
|
|||
}
|
|||
fn cause(&self) -> Option<&Error> {
|
|||
match self {
|
|||
&SystemdUserSessionError::ExecError(ref e) => Some(e)
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl<E: Error> fmt::Display for SystemdUserSessionError<E> {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|||
write!(f, "{}", self.description())
|
|||
}
|
|||
}
|
|||
|
|||
pub struct SystemdUserSession<'a> {
|
|||
user_name: &'a str,
|
|||
command_runner: &'a CommandRunner
|
|||
}
|
|||
|
|||
impl<'a> SystemdUserSession<'a> {
|
|||
pub fn new(user_name: &'a str, command_runner: &'a CommandRunner) -> Self {
|
|||
SystemdUserSession {
|
|||
user_name: user_name,
|
|||
command_runner: command_runner
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl<'a> Symbol for SystemdUserSession<'a> {
|
|||
type Error = SystemdUserSessionError<IoError>;
|
|||
fn target_reached(&self) -> Result<bool, Self::Error> {
|
|||
let mut path = PathBuf::from("/var/lib/systemd/linger");
|
|||
path.push(self.user_name);
|
|||
Ok(path.exists())
|
|||
}
|
|||
|
|||
fn execute(&self) -> Result<(), Self::Error> {
|
|||
match self.command_runner.run_with_args("loginctl", &["enable-linger", self.user_name]) {
|
|||
Ok(_) => Ok(()),
|
|||
Err(e) => Err(SystemdUserSessionError::ExecError(e))
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl<'a> fmt::Display for SystemdUserSession<'a> {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error>{
|
|||
write!(f, "Systemd user session for {}", self.user_name)
|
|||
}
|
|||
}
|
@ -0,0 +1,187 @@ |
|||
use std::error::Error;
|
|||
use std::fmt;
|
|||
use std::io::Error as IoError;
|
|||
|
|||
use command_runner::CommandRunner;
|
|||
use symbols::Symbol;
|
|||
|
|||
#[derive(Debug, PartialEq)]
|
|||
pub enum UserAdderError<E: Error> {
|
|||
AlreadyExists,
|
|||
UnknownError,
|
|||
ImplError(E)
|
|||
}
|
|||
|
|||
impl<E: Error> Error for UserAdderError<E> {
|
|||
fn description(&self) -> &str {
|
|||
match self {
|
|||
&UserAdderError::AlreadyExists => "User already exists",
|
|||
&UserAdderError::UnknownError => "Unknown error",
|
|||
&UserAdderError::ImplError(_) => "User adding error"
|
|||
}
|
|||
}
|
|||
fn cause(&self) -> Option<&Error> {
|
|||
match self {
|
|||
&UserAdderError::ImplError(ref e) => Some(e),
|
|||
_ => None
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl<E: Error> fmt::Display for UserAdderError<E> {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
match self.cause() {
|
|||
Some(e) => write!(f, "{} (cause: {})", self.description(), e),
|
|||
None => write!(f, "{}", self.description())
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
pub trait UserAdder {
|
|||
type SubE: Error;
|
|||
fn add_user(&self, user_name: &str) -> Result<(), UserAdderError<Self::SubE>>;
|
|||
}
|
|||
|
|||
#[derive(Debug, PartialEq)]
|
|||
pub enum UserError<E: Error> {
|
|||
GenericError,
|
|||
ExecError(E)
|
|||
}
|
|||
|
|||
impl<E: Error> Error for UserError<E> {
|
|||
fn description(&self) -> &str {
|
|||
match self {
|
|||
&UserError::GenericError => "Could not find out if user exists",
|
|||
&UserError::ExecError(_) => "Error executing symbol"
|
|||
}
|
|||
}
|
|||
fn cause(&self) -> Option<&Error> {
|
|||
match self {
|
|||
&UserError::ExecError(ref e) => Some(e),
|
|||
_ => None
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl<E: Error> fmt::Display for UserError<E> {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
match self.cause() {
|
|||
Some(e) => write!(f, "{} (cause: {})", self.description(), e),
|
|||
None => write!(f, "{}", self.description())
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
pub struct User<'a, E, A> where E: Error + Sized, A: 'a + UserAdder<SubE=E> {
|
|||
user_name: &'a str,
|
|||
command_runner: &'a CommandRunner,
|
|||
user_adder: &'a A
|
|||
}
|
|||
|
|||
impl<'a, E: Error + Sized, A: 'a + UserAdder<SubE=E>> User<'a, E, A> {
|
|||
pub fn new(user_name: &'a str, command_runner: &'a CommandRunner, user_adder: &'a A) -> User<'a, E, A> {
|
|||
User {
|
|||
user_name: user_name,
|
|||
command_runner: command_runner,
|
|||
user_adder: user_adder
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl<'a, E: Error, A: UserAdder<SubE=E>> fmt::Display for User<'a, E, A> {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
write!(f, "User {}", self.user_name)
|
|||
}
|
|||
}
|
|||
|
|||
impl<'a, E: Error, A: UserAdder<SubE=E>> Symbol for User<'a, E, A> {
|
|||
type Error = UserError<UserAdderError<E>>;
|
|||
fn target_reached(&self) -> Result<bool, Self::Error> {
|
|||
let output = self.command_runner.run_with_args("getent", &["passwd", self.user_name]);
|
|||
match output {
|
|||
Ok(output) => match output.status.code() {
|
|||
Some(2) => Ok(false),
|
|||
Some(0) => Ok(true),
|
|||
_ => Err(UserError::GenericError)
|
|||
},
|
|||
Err(_) => Err(UserError::GenericError)
|
|||
}
|
|||
}
|
|||
|
|||
fn execute(&self) -> Result<(), Self::Error> {
|
|||
self.user_adder.add_user(self.user_name).map_err(|e| UserError::ExecError(e))
|
|||
}
|
|||
}
|
|||
|
|||
pub struct SystemUserAdder<'a> {
|
|||
command_runner: &'a CommandRunner
|
|||
}
|
|||
|
|||
impl<'a> SystemUserAdder<'a> {
|
|||
pub fn new(command_runner: &'a CommandRunner) -> SystemUserAdder<'a> {
|
|||
SystemUserAdder { command_runner: command_runner }
|
|||
}
|
|||
}
|
|||
|
|||
impl<'a> UserAdder for SystemUserAdder<'a> {
|
|||
type SubE = IoError;
|
|||
fn add_user(&self, user_name: &str) -> Result<(), UserAdderError<IoError>> {
|
|||
let output = self.command_runner.run_with_args("adduser", &["--system", "--disabled-login", "--disabled-password", user_name]);
|
|||
match output {
|
|||
Ok(output) => match output.status.code() {
|
|||
Some(0) => Ok(()),
|
|||
Some(1) => Err(UserAdderError::AlreadyExists),
|
|||
Some(_) => Err(UserAdderError::UnknownError),
|
|||
None => Err(UserAdderError::UnknownError),
|
|||
},
|
|||
Err(e) => Err(UserAdderError::ImplError(e))
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
mod test {
|
|||
use std::error::Error;
|
|||
use std::fmt;
|
|||
|
|||
use command_runner::StdCommandRunner;
|
|||
use symbols::Symbol;
|
|||
use symbols::user::User;
|
|||
use symbols::user::UserAdder;
|
|||
use symbols::user::UserAdderError;
|
|||
|
|||
#[derive(Debug, PartialEq)]
|
|||
struct DummyError;
|
|||
impl Error for DummyError {
|
|||
fn description(&self) -> &str {
|
|||
"DummyError"
|
|||
}
|
|||
}
|
|||
|
|||
impl fmt::Display for DummyError {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
write!(f, "DummyError")
|
|||
}
|
|||
}
|
|||
|
|||
struct DummyUserAdder;
|
|||
|
|||
impl UserAdder for DummyUserAdder {
|
|||
type SubE = DummyError;
|
|||
fn add_user(&self, user_name: &str) -> Result<(), UserAdderError<Self::SubE>> {
|
|||
Ok(())
|
|||
}
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn test_target_reached_nonexisting() {
|
|||
let symbol = User { user_name: "nonexisting", command_runner: &StdCommandRunner, user_adder: &DummyUserAdder };
|
|||
assert_eq!(symbol.target_reached(), Ok(false));
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn test_target_reached_root() {
|
|||
let symbol = User { user_name: "root", command_runner: &StdCommandRunner, user_adder: &DummyUserAdder };
|
|||
assert_eq!(symbol.target_reached(), Ok(true));
|
|||
}
|
|||
}
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue