New architecture

This commit is contained in:
Adrian Heine 2019-12-26 20:50:23 +01:00
parent e4b3424ba6
commit 907a4962c5
61 changed files with 2742 additions and 3100 deletions

View file

@ -1,85 +0,0 @@
use std::error::Error;
use std::fmt;
use std::path::Path;
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct AcmeAccountKey<'a, P: AsRef<Path>, C: CommandRunner> {
path: P,
command_runner: &'a C,
}
impl<'a, P: AsRef<Path>, C: CommandRunner> AcmeAccountKey<'a, P, C> {
pub fn new(path: P, command_runner: &'a C) -> Self {
AcmeAccountKey {
path,
command_runner,
}
}
fn get_bytes(&self) -> u32 {
4096
}
}
impl<P: AsRef<Path>, C: CommandRunner> fmt::Display for AcmeAccountKey<'_, P, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AcmeAccountKey {}", self.path.as_ref().display())
}
}
impl<P: AsRef<Path>, C: CommandRunner> Symbol for AcmeAccountKey<'_, P, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.path.as_ref().exists() {
return Ok(false);
}
let stdout = self.command_runner.get_output(
"openssl",
args![
"rsa",
"-in",
self.path.as_ref(),
"-noout",
"-check",
"-text",
],
)?;
Ok(stdout.ends_with("RSA key ok\n".as_bytes()))
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"openssl",
args![
"genrsa",
"-out",
self.path.as_ref(),
self.get_bytes().to_string(),
],
)
}
fn get_prerequisites(&self) -> Vec<Resource> {
if let Some(parent) = self.path.as_ref().parent() {
vec![Resource::new("dir", parent.to_str().unwrap())]
} else {
vec![]
}
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]
mod test {}

View file

@ -1,80 +1,61 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use std::borrow::Borrow;
use std::error::Error;
use std::fmt;
use std::fs::File as FsFile;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::marker::PhantomData;
use std::path::Path;
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct AcmeCert<
'a,
D: AsRef<str>,
R: AsRef<Path>,
C: CommandRunner,
K: AsRef<Path>,
CH: AsRef<Path>,
> {
#[derive(Debug)]
pub struct Cert<_C, C, D, P> {
domain: D,
command_runner: &'a C,
root_cert_path: R,
account_key_path: K,
challenges_path: CH,
command_runner: C,
root_cert_path: P,
account_key_path: P,
challenges_path: P,
csr_path: P,
cert_path: P,
phantom: PhantomData<_C>,
}
impl<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<Path>>
AcmeCert<'a, D, R, C, K, CH>
{
impl<_C, C, D, P> Cert<_C, C, D, P> {
pub fn new(
domain: D,
command_runner: &'a C,
root_cert_path: R,
account_key_path: K,
challenges_path: CH,
command_runner: C,
root_cert_path: P,
account_key_path: P,
challenges_path: P,
csr_path: P,
cert_path: P,
) -> Self {
AcmeCert {
Self {
domain,
command_runner,
root_cert_path,
account_key_path,
challenges_path,
csr_path,
cert_path,
phantom: PhantomData::default(),
}
}
fn get_csr_path(&self) -> PathBuf {
format!("/etc/ssl/local_certs/{}.csr", self.domain.as_ref()).into()
}
fn get_cert_path(&self) -> PathBuf {
format!("/etc/ssl/local_certs/{}.crt", self.domain.as_ref()).into()
}
}
impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<Path>> fmt::Display
for AcmeCert<'_, D, R, C, K, CH>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AcmeCert {}", self.domain.as_ref())
}
}
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<Path>> Symbol
for AcmeCert<'_, D, R, C, K, CH>
{
impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef<str>, P: AsRef<Path>> Symbol for Cert<_C, C, D, P> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.get_cert_path().exists() {
if !self.cert_path.as_ref().exists() {
return Ok(false);
}
let output = self.command_runner.run_with_args(
let output = self.command_runner.borrow().run_with_args(
"openssl",
args![
"x509",
"-in",
self.get_cert_path(),
self.cert_path.as_ref(),
"-noout",
"-subject",
"-checkend",
@ -92,13 +73,14 @@ impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<
Ok(
self
.command_runner
.borrow()
.run_successfully(
"openssl",
args![
"verify",
"--untrusted",
self.root_cert_path.as_ref(),
self.get_cert_path(),
self.cert_path.as_ref(),
],
)
.is_ok(),
@ -118,36 +100,21 @@ impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
let output = self.command_runner.get_output(
let output = self.command_runner.borrow().get_output(
"acme-tiny",
args![
"--account-key",
self.account_key_path.as_ref(),
"--csr",
self.get_csr_path(),
self.csr_path.as_ref(),
"--acme-dir",
self.challenges_path.as_ref(),
],
)?;
let mut file = FsFile::create(self.get_cert_path())?;
let mut file = FsFile::create(self.cert_path.as_ref())?;
file.write_all(&output)?;
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![Resource::new("file", self.get_csr_path().to_str().unwrap())]
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]

View file

@ -1,117 +0,0 @@
use std::error::Error;
use std::fmt;
use std::fs::File as FsFile;
use std::io::Write;
use std::path::{Path, PathBuf};
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct AcmeCertChain<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner> {
domain: D,
command_runner: &'a C,
root_cert: R,
}
impl<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner> AcmeCertChain<'a, D, R, C> {
pub fn new(domain: D, command_runner: &'a C, root_cert: R) -> Self {
AcmeCertChain {
domain,
command_runner,
root_cert,
}
}
fn get_single_cert_path(&self) -> PathBuf {
format!("/etc/ssl/local_certs/{}.crt", self.domain.as_ref()).into()
}
fn get_cert_chain_path(&self) -> PathBuf {
format!("/etc/ssl/local_certs/{}.chained.crt", self.domain.as_ref()).into()
}
}
impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner> fmt::Display for AcmeCertChain<'_, D, R, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AcmeCertChain {}", self.domain.as_ref())
}
}
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
impl<D: AsRef<str>, R: AsRef<Path>, C: CommandRunner> Symbol for AcmeCertChain<'_, D, R, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.get_cert_chain_path().exists() {
return Ok(false);
}
let stdout = self.command_runner.get_output(
"openssl",
args![
"x509",
"-in",
self.get_cert_chain_path(),
"-noout",
"-subject",
"-checkend",
(30 * DAYS_IN_SECONDS).to_string(),
],
)?;
if stdout
!= format!(
"subject=CN = {}\nCertificate will not expire\n",
self.domain.as_ref()
)
.as_bytes()
{
return Ok(false);
}
// FIXME: From my understanding, the -untrusted *.pem parameter shouldn't be necessary, but is necessary with openssl 1.1.0f-3
Ok(
self
.command_runner
.run_successfully(
"openssl",
args![
"verify",
"-untrusted",
self.root_cert.as_ref(),
self.get_cert_chain_path(),
],
)
.is_ok(),
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
let output = self.command_runner.get_output(
"cat",
args![self.get_single_cert_path(), self.root_cert.as_ref(),],
)?;
let mut file = FsFile::create(self.get_cert_chain_path())?;
file.write_all(&output)?;
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![Resource::new(
"file",
self.get_single_cert_path().to_str().unwrap(),
)]
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]
mod test {}

View file

@ -1,113 +1,2 @@
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use crate::command_runner::CommandRunner;
use crate::command_runner::SetuidCommandRunner;
use crate::symbols::concat::Concat;
use crate::symbols::dir::Dir;
use crate::symbols::file::File;
use crate::symbols::list::List;
use crate::symbols::owner::Owner;
use crate::symbols::Symbol;
mod account_key;
mod cert;
mod chain;
pub use self::account_key::AcmeAccountKey;
pub use self::cert::AcmeCert;
pub use self::chain::AcmeCertChain;
const ROOT_CERT_FILE_NAME: &str = "lets_encrypt_x3_cross_signed.pem";
const ACCOUNT_KEY_FILE_NAME: &str = "account.key";
pub struct Factory<'a, U: AsRef<str>, H: AsRef<Path>, C: AsRef<str>, R: CommandRunner> {
user_name: U,
home_dir: H,
cert: C,
command_runner: &'a R,
acme_command_runner: SetuidCommandRunner<'a, U, R>,
}
impl<'a, U: Clone + AsRef<str>, H: AsRef<Path>, C: AsRef<str>, R: CommandRunner>
Factory<'a, U, H, C, R>
{
pub fn new(user_name: U, home_dir: H, cert: C, command_runner: &'a R) -> Self {
let acme_command_runner = SetuidCommandRunner::new(user_name.clone(), command_runner);
Self {
user_name,
home_dir,
cert,
command_runner,
acme_command_runner,
}
}
pub fn get_challenges_dir(&'a self) -> Cow<'_, Path> {
[self.home_dir.as_ref(), "challenges".as_ref()]
.iter()
.collect::<PathBuf>()
.into()
}
pub fn get_init(&'a self) -> impl Symbol + 'a {
let root_cert_path: PathBuf = [self.home_dir.as_ref(), ROOT_CERT_FILE_NAME.as_ref()]
.iter()
.collect();
let account_key_file: PathBuf = [self.home_dir.as_ref(), ACCOUNT_KEY_FILE_NAME.as_ref()]
.iter()
.collect();
List::from((
AcmeAccountKey::new(account_key_file.clone(), self.command_runner),
Owner::new(
account_key_file,
self.user_name.clone(),
self.command_runner,
),
Dir::new(self.get_challenges_dir()),
Owner::new(
self.get_challenges_dir(),
self.user_name.clone(),
self.command_runner,
),
Dir::new("/etc/ssl/local_certs"),
Owner::new(
"/etc/ssl/local_certs",
self.user_name.clone(),
self.command_runner,
),
File::new(root_cert_path, self.cert.as_ref()),
))
}
pub fn get_cert<HOST: 'a + Clone + AsRef<str>>(&'a self, host: HOST) -> impl Symbol + 'a {
let root_cert_path: PathBuf = [self.home_dir.as_ref(), ROOT_CERT_FILE_NAME.as_ref()]
.iter()
.collect();
let account_key_path: PathBuf = [self.home_dir.as_ref(), ACCOUNT_KEY_FILE_NAME.as_ref()]
.iter()
.collect();
List::from((
AcmeCert::new(
host.clone(),
&self.acme_command_runner,
root_cert_path.clone(),
account_key_path,
self.get_challenges_dir(),
),
AcmeCertChain::new(host, &self.acme_command_runner, root_cert_path),
))
}
pub fn get_key_and_cert_bundle<HOST: 'a + Clone + AsRef<str>>(
&'a self,
host: HOST,
) -> impl Symbol + 'a {
List::from((
self.get_cert(host.clone()),
Concat::new(
[
format!("/etc/ssl/private/{}.key", host.as_ref()),
format!("/etc/ssl/local_certs/{}.chained.crt", host.as_ref()),
],
format!("/etc/ssl/private/{}.with_key.crt", host.as_ref()),
),
))
}
}
pub use self::cert::Cert;

View file

@ -1,13 +1,11 @@
use crate::symbols::Symbol;
use std::error::Error;
use std::fmt;
use std::fs::{metadata, File};
use std::io::copy;
use std::marker::PhantomData;
use std::path::Path;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
#[derive(Debug)]
pub struct Concat<S, D, I> {
target: D,
sources: S,
@ -46,34 +44,4 @@ impl<S: AsRef<[I]>, D: AsRef<Path>, I: AsRef<Path>> Symbol for Concat<S, D, I> {
}
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
let mut r: Vec<Resource> = self
.sources
.as_ref()
.iter()
.map(|s| Resource::new("file", s.as_ref().to_str().unwrap()))
.collect();
if let Some(parent) = self.target.as_ref().parent() {
r.push(Resource::new("dir", parent.to_str().unwrap()))
}
r
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<S, D: AsRef<Path>, I> fmt::Display for Concat<S, D, I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Concat {}", self.target.as_ref().display())
}
}

View file

@ -1,19 +1,17 @@
use std::error::Error;
use std::fmt;
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use std::error::Error;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct Cron<'r, C: AsRef<str>, U: AsRef<str>, R: CommandRunner> {
#[derive(Debug)]
pub struct Cron<'r, C, U, R> {
user: U,
content: C,
command_runner: &'r R,
}
impl<'r, U: AsRef<str>, R: CommandRunner> Cron<'r, String, U, R> {
impl<'r, U, R> Cron<'r, String, U, R> {
pub fn new<C: AsRef<str>>(user: U, content: C, command_runner: &'r R) -> Self {
Cron {
Self {
user,
content: String::from(content.as_ref()) + "\n",
command_runner,
@ -40,21 +38,4 @@ impl<C: AsRef<str>, U: AsRef<str>, R: CommandRunner> Symbol for Cron<'_, C, U, R
}
Ok(())
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<C: AsRef<str>, U: AsRef<str>, R: CommandRunner> fmt::Display for Cron<'_, C, U, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Cron {}", self.user.as_ref())
}
}

View file

@ -1,23 +1,21 @@
use crate::symbols::Symbol;
use std::error::Error;
use std::fmt;
use std::fs;
use std::io;
use std::path::Path;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct Dir<D: AsRef<Path>> {
path: D,
#[derive(Debug)]
pub struct Dir<P> {
path: P,
}
impl<D: AsRef<Path>> Dir<D> {
pub fn new(path: D) -> Self {
impl<P> Dir<P> {
pub fn new(path: P) -> Self {
Self { path }
}
}
impl<D: AsRef<Path>> Symbol for Dir<D> {
impl<P: AsRef<Path>> Symbol for Dir<P> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.path.as_ref().exists() {
return Ok(false);
@ -35,36 +33,4 @@ impl<D: AsRef<Path>> Symbol for Dir<D> {
fn execute(&self) -> Result<(), Box<dyn Error>> {
fs::create_dir(self.path.as_ref()).map_err(|e| Box::new(e) as Box<dyn Error>)
}
fn get_prerequisites(&self) -> Vec<Resource> {
if let Some(parent) = self.path.as_ref().parent() {
vec![Resource::new("dir", parent.to_str().unwrap())]
} else {
vec![]
}
}
fn provides(&self) -> Option<Vec<Resource>> {
Some(vec![Resource::new(
"dir",
self.path.as_ref().to_str().unwrap(),
)])
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<D: AsRef<Path>> fmt::Display for Dir<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Dir {}", self.path.as_ref().display())
}
}

View file

@ -1,436 +0,0 @@
use std::borrow::Cow;
use std::path::Path;
use std::path::PathBuf;
use crate::command_runner::CommandRunner;
use crate::storage::{SimpleStorage, Storage};
use crate::symbols::acme::Factory as AcmeFactory;
use crate::symbols::cron::Cron;
use crate::symbols::file::File;
use crate::symbols::git::checkout::GitCheckout;
use crate::symbols::hook::Hook;
use crate::symbols::list::List;
use crate::symbols::mariadb::{DatabaseDump, MariaDBDatabase, MariaDBUser};
use crate::symbols::nginx::server::{server_config, NginxServer};
use crate::symbols::owner::Owner;
use crate::symbols::stored_directory::{StorageDirection, StoredDirectory};
use crate::symbols::systemd::reload::ReloadService;
use crate::symbols::systemd::user_service::UserService;
use crate::symbols::tls::SelfSignedTlsCert;
use crate::symbols::Symbol;
pub trait Policy {
fn user_name_for_host(&self, host_name: &'static str) -> String {
host_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part)
}
fn home_for_user(&self, user_name: &str) -> PathBuf {
format!("/home/{}", user_name).into()
}
fn socket_path(&self, user_name: &str, service_name: &str) -> PathBuf {
format!("/var/tmp/{}-{}.socket", user_name, service_name).into()
}
fn acme_user(&self) -> Cow<'_, str> {
"acme".into()
}
fn php_version(&self) -> &'static str {
"7.0"
}
}
pub struct DefaultPolicy;
impl Policy for DefaultPolicy {}
pub struct SymbolFactory<'a, C: CommandRunner, P: Policy> {
command_runner: &'a C,
acme_factory: AcmeFactory<'a, Cow<'a, str>, PathBuf, &'a str, C>,
policy: &'a P,
}
impl<'b, C: 'b + CommandRunner, P: 'b + Policy> SymbolFactory<'b, C, P> {
pub fn new(command_runner: &'b C, policy: &'b P, cert: &'b str) -> Self {
let acme_user = policy.acme_user();
let acme_home = policy.home_for_user(&acme_user);
let acme_factory = AcmeFactory::new(acme_user, acme_home, cert, command_runner);
SymbolFactory {
command_runner,
acme_factory,
policy,
}
}
pub fn get_cert<'a, H: 'a + AsRef<str> + Clone>(&'a self, host: H) -> impl Symbol + 'a {
self.acme_factory.get_cert(host)
}
pub fn get_key_and_cert_bundle<'a, H: 'a + AsRef<str> + Clone>(
&'a self,
host: H,
) -> impl Symbol + 'a {
self.acme_factory.get_key_and_cert_bundle(host)
}
pub fn get_nginx_acme_server<'a, S: 'a + Symbol>(
&'a self,
host: &'a str,
nginx_server_symbol: S,
) -> impl Symbol + 'a {
List::from((
SelfSignedTlsCert::new(host, self.command_runner),
Hook::new(
nginx_server_symbol,
ReloadService::new("nginx", self.command_runner),
),
Hook::new(
self.get_cert(host),
ReloadService::new("nginx", self.command_runner),
),
))
}
pub fn get_nginx_acme_challenge_config<'a>(&'a self) -> impl Symbol + 'a {
File::new(
"/etc/nginx/snippets/acme-challenge.conf",
format!(
"location ^~ /.well-known/acme-challenge/ {{
alias {}/;
try_files $uri =404;
}}",
self.acme_factory.get_challenges_dir().to_str().unwrap()
),
)
}
fn get_php_fpm_pool_socket_path<'a>(&'a self, user_name: &str) -> PathBuf {
format!("/run/php/{}.sock", user_name).into()
}
fn get_php_fpm_pool<'a>(&'a self, user_name: &str, max_children: usize) -> impl Symbol + 'a {
let socket = self.get_php_fpm_pool_socket_path(user_name);
let php_version = self.policy.php_version();
Hook::new(
File::new(
format!("/etc/php/{}/fpm/pool.d/{}.conf", php_version, user_name),
format!(
"[{0}]
user = {0}
group = www-data
listen = {1}
listen.owner = www-data
pm = ondemand
pm.max_children = {2}
catch_workers_output = yes
env[PATH] = /usr/local/bin:/usr/bin:/bin
",
user_name,
socket.to_str().unwrap(),
max_children
),
),
ReloadService::new(format!("php{}-fpm", php_version), self.command_runner),
)
}
pub fn serve_php<'a, ROOT: AsRef<Path>>(
&'a self,
host_name: &'static str,
root_dir: ROOT,
max_children: usize,
additional_config: &'a str,
) -> impl Symbol + 'a {
let user_name = self.policy.user_name_for_host(host_name);
let socket = self.get_php_fpm_pool_socket_path(&user_name);
List::from((
self.get_php_fpm_pool(&user_name, max_children),
self.get_nginx_acme_server(
host_name,
NginxServer::new_php(
host_name,
socket,
root_dir,
self.command_runner,
additional_config,
),
),
))
}
pub fn serve_wordpress<'a, ROOT: 'a + AsRef<Path>>(
&'a self,
host_name: &'static str,
root_dir: ROOT,
) -> impl Symbol + 'a {
self.serve_php(
host_name,
root_dir,
10,
"
location / {
try_files $uri $uri/ /index.php?$args;
}
",
)
}
pub fn serve_dokuwiki<'a>(
&'a self,
host_name: &'static str,
root_dir: &'static str,
) -> impl Symbol + 'a {
let user_name = self.policy.user_name_for_host(host_name);
let socket = self.get_php_fpm_pool_socket_path(&user_name);
List::from((
self.get_php_fpm_pool(&user_name, 10),
self.get_nginx_acme_server(host_name,
NginxServer::new(
host_name,
server_config(host_name, &format!("
root {};
index doku.php;
location ~ [^/]\\.php(/|$) {{
fastcgi_pass unix:{};
include \"snippets/fastcgi-php.conf\";
}}
location ~ /(data/|conf/|bin/|inc/|install.php) {{ deny all; }}
location / {{ try_files $uri $uri/ @dokuwiki; }}
location @dokuwiki {{
# rewrites \"doku.php/\" out of the URLs if you set the userewrite setting to .htaccess in dokuwiki config page
rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last;
rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
rewrite ^/(.*) /doku.php?id=$1&$args last;
}}
",
root_dir,
socket.to_str().unwrap())),
self.command_runner
))
))
}
pub fn serve_nextcloud<'a, ROOT: 'a + AsRef<Path>>(
&'a self,
host_name: &'static str,
root_dir: ROOT,
) -> impl Symbol + 'a {
self.serve_php(
host_name,
root_dir,
25,
"
client_max_body_size 500M;
# Disable gzip to avoid the removal of the ETag header
gzip off;
rewrite ^/caldav(.*)$ /remote.php/caldav$1 redirect;
rewrite ^/carddav(.*)$ /remote.php/carddav$1 redirect;
rewrite ^/webdav(.*)$ /remote.php/webdav$1 redirect;
error_page 403 /core/templates/403.php;
error_page 404 /core/templates/404.php;
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ~ ^/(?:\\.htaccess|data|config|db_structure\\.xml|README) {
deny all;
}
location / {
# The following 2 rules are only needed with webfinger
rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
rewrite ^/.well-known/carddav /remote.php/carddav/ redirect;
rewrite ^/.well-known/caldav /remote.php/caldav/ redirect;
rewrite ^(/core/doc/[^\\/]+/)$ $1/index.html;
try_files $uri $uri/ /index.php;
}
# Adding the cache control header for js and css files
# Make sure it is BELOW the location ~ \\.php(?:$|/) { block
location ~* \\.(?:css|js)$ {
add_header Cache-Control \"public, max-age=7200\";
# Optional: Don't log access to assets
access_log off;
}
# Optional: Don't log access to other assets
location ~* \\.(?:jpg|jpeg|gif|bmp|ico|png|swf)$ {
access_log off;
}
",
)
}
pub fn serve_redir<'a>(
&'a self,
host_name: &'static str,
target: &'static str,
) -> impl Symbol + 'a {
self.get_nginx_acme_server(
host_name,
NginxServer::new_redir(host_name, target, self.command_runner),
)
}
pub fn serve_static<'a>(&'a self, host_name: &'static str, dir: &'a str) -> impl Symbol + 'a {
self.get_nginx_acme_server(
host_name,
NginxServer::new_static(host_name, dir, self.command_runner),
)
}
pub fn get_stored_directory<'a, T: Into<String>>(
&'a self,
storage_name: &'static str,
target: T,
) -> (impl Symbol + 'a, impl Symbol + 'a) {
let data = SimpleStorage::new("/root/data".to_string(), storage_name.to_string());
let string_target = target.into();
(
StoredDirectory::new(
string_target.clone(),
data.clone(),
StorageDirection::Save,
self.command_runner,
),
StoredDirectory::new(
string_target,
data,
StorageDirection::Load,
self.command_runner,
),
)
}
pub fn get_mariadb_database<'a>(&'a self, name: &'static str) -> impl Symbol + 'a {
let db_dump = SimpleStorage::new("/root/data".to_string(), format!("{}.sql", name));
List::from((
MariaDBDatabase::new(
name,
db_dump.read_filename().expect("Initial db dump missing"),
self.command_runner,
),
DatabaseDump::new(name, db_dump, self.command_runner),
))
}
pub fn get_mariadb_user<'a>(&'a self, user_name: &'static str) -> impl Symbol + 'a {
MariaDBUser::new(user_name, self.command_runner)
}
pub fn get_git_checkout<'a, T: 'a + AsRef<Path>>(
&'a self,
target: T,
source: &'a str,
branch: &'a str,
) -> impl Symbol + 'a {
GitCheckout::new(target, source, branch, self.command_runner)
}
pub fn get_owner<'a, U: 'a + AsRef<str>, F: 'a + AsRef<Path>>(
&'a self,
file: F,
user: U,
) -> impl Symbol + 'a {
Owner::new(file, user, self.command_runner)
}
pub fn get_file<'a, F: 'a + AsRef<str>, Q: 'a + AsRef<Path>>(
&'a self,
path: Q,
content: F,
) -> impl Symbol + 'a {
File::new(path, content)
}
pub fn get_cron<'a, T: 'a + AsRef<str>, U: 'a + AsRef<str>>(
&'a self,
user: U,
content: T,
) -> impl Symbol + 'a {
Cron::new(user, content, self.command_runner)
}
pub fn get_acme_user<'a>(&'a self) -> impl Symbol + 'a {
self.acme_factory.get_init()
}
pub fn get_systemd_user_service<'a, U: 'a + AsRef<str> + Clone>(
&'a self,
user_name: U,
service_name: &'a str,
config: String,
) -> impl Symbol + 'a {
let socket_path = self.policy.socket_path(user_name.as_ref(), service_name);
let home = self.policy.home_for_user(user_name.as_ref());
UserService::new(
socket_path,
&home,
user_name,
service_name,
self.command_runner,
config,
)
}
fn get_nodejs_systemd_user_service<'a, U: 'a + AsRef<str> + Clone>(
&'a self,
user_name: U,
service_name: &'a str,
path: &'a Path,
) -> impl Symbol + 'a {
let socket_path = self.policy.socket_path(user_name.as_ref(), service_name);
let home = self.policy.home_for_user(user_name.as_ref());
UserService::new_nodejs(
&home,
user_name,
service_name,
path,
self.command_runner,
socket_path,
)
}
pub fn proxy_socket<'a, T: AsRef<Path>>(
&'a self,
host_name: &'static str,
service_name: &'a str,
root_dir: T,
) -> impl Symbol + 'a {
let user_name = self.policy.user_name_for_host(host_name);
let socket_path = self.policy.socket_path(&user_name, service_name);
self.get_nginx_acme_server(
host_name,
NginxServer::new_proxy(host_name, &socket_path, root_dir, self.command_runner),
)
}
pub fn serve_nodejs<'a, 'c: 'a, S: 'a + Symbol, SP: 'a + AsRef<Path>>(
&'c self,
host: &'static str,
service_name: &'a str,
path: &'a Path,
static_path: SP,
nodejs_symbol: S,
) -> impl Symbol + 'a {
let user_name = self.policy.user_name_for_host(host);
List::from((
Hook::new(
nodejs_symbol,
self.get_nodejs_systemd_user_service(user_name, service_name, path),
),
self.proxy_socket(host, service_name, static_path),
))
}
}

View file

@ -1,26 +1,22 @@
use crate::symbols::Symbol;
use std::error::Error;
use std::fmt;
use std::fs::File as FsFile;
use std::io::{Read, Write};
use std::path::Path;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct File<C: AsRef<str>, D: AsRef<Path>> {
#[derive(Debug)]
pub struct File<D, C> {
path: D,
content: C,
}
impl<C: AsRef<str>, D: AsRef<Path>> File<C, D> {
impl<D, C> File<D, C> {
pub fn new(path: D, content: C) -> Self {
Self { path, content }
}
}
impl<C: AsRef<str>, D: AsRef<Path>> Symbol for File<C, D> {
impl<D: AsRef<Path>, C: AsRef<str>> Symbol for File<D, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.path.as_ref().exists() {
return Ok(false);
@ -44,29 +40,4 @@ impl<C: AsRef<str>, D: AsRef<Path>> Symbol for File<C, D> {
file.write_all(self.content.as_ref().as_bytes())?;
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
if let Some(parent) = self.path.as_ref().parent() {
vec![Resource::new("dir", parent.to_str().unwrap())]
} else {
vec![]
}
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<C: AsRef<str>, D: AsRef<Path>> fmt::Display for File<C, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "File {}", self.path.as_ref().display())
}
}

View file

@ -1,62 +1,48 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use std::borrow::Borrow;
use std::error::Error;
use std::ffi::OsStr;
use std::fmt;
use std::fs::metadata;
use std::io;
use std::marker::PhantomData;
use std::path::Path;
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct GitCheckout<'a, C: CommandRunner, T: AsRef<Path>> {
target: T,
source: &'a str,
branch: &'a str,
command_runner: &'a C,
#[derive(Debug)]
pub struct Checkout<_C, C, P, S, B> {
target: P,
source: S,
branch: B,
command_runner: C,
phantom: PhantomData<_C>,
}
impl<'a, C: CommandRunner, T: AsRef<Path>> GitCheckout<'a, C, T> {
pub fn new(target: T, source: &'a str, branch: &'a str, command_runner: &'a C) -> Self {
GitCheckout {
impl<C, _C, P, S, B> Checkout<_C, C, P, S, B> {
pub fn new(target: P, source: S, branch: B, command_runner: C) -> Self {
Self {
target,
source,
branch,
command_runner,
phantom: PhantomData::default(),
}
}
}
impl<C: CommandRunner, T: AsRef<Path>> fmt::Display for GitCheckout<'_, C, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Checkout {} (branch {}) into {}",
self.source,
self.branch,
self.target.as_ref().display()
)
}
}
impl<C: CommandRunner, T: AsRef<Path>> GitCheckout<'_, C, T> {
fn _run_in_target_repo<S: AsRef<OsStr>>(&self, args: &[S]) -> Result<Vec<u8>, Box<dyn Error>> {
impl<C: CommandRunner, _C: Borrow<C>, P: AsRef<Path>, S, B> Checkout<C, _C, P, S, B> {
fn _run_in_target_repo(&self, args: &[impl AsRef<OsStr>]) -> Result<Vec<u8>, Box<dyn Error>> {
let mut new_args = vec![OsStr::new("-C"), self.target.as_ref().as_ref()];
new_args.extend(args.iter().map(AsRef::as_ref));
self.command_runner.get_output("git", &new_args)
self.command_runner.borrow().get_output("git", &new_args)
}
}
impl<C: CommandRunner, T: AsRef<Path>> Symbol for GitCheckout<'_, C, T> {
impl<C: CommandRunner, _C: Borrow<C>, P: AsRef<Path>, S: AsRef<str>, B: AsRef<str>> Symbol
for Checkout<C, _C, P, S, B>
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if let Err(e) = metadata(self.target.as_ref()) {
return if e.kind() == io::ErrorKind::NotFound {
Ok(false)
} else {
Err(Box::new(e))
};
if !self.target.as_ref().exists() {
return Ok(false);
}
self._run_in_target_repo(&["fetch", self.source, self.branch])?;
self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?;
// git rev-list resolves tag objects
let fetch_head = self._run_in_target_repo(&["rev-list", "-1", "FETCH_HEAD"])?;
let head = self._run_in_target_repo(&["rev-list", "-1", "HEAD"])?;
@ -65,48 +51,23 @@ impl<C: CommandRunner, T: AsRef<Path>> Symbol for GitCheckout<'_, C, T> {
fn execute(&self) -> Result<(), Box<dyn Error>> {
if !self.target.as_ref().exists() {
return self.command_runner.run_successfully(
return self.command_runner.borrow().run_successfully(
"git",
&[
OsStr::new("clone"),
"--depth".as_ref(),
"1".as_ref(),
"-b".as_ref(),
args![
"clone",
"--depth",
"1",
"-b",
self.branch.as_ref(),
self.source.as_ref(),
self.target.as_ref().as_ref(),
self.target.as_ref(),
],
);
}
self._run_in_target_repo(&["fetch", self.source, self.branch])?;
self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?;
self._run_in_target_repo(&["merge", "FETCH_HEAD"])?;
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![Resource::new(
"dir",
self.target.as_ref().parent().unwrap().to_string_lossy(),
)]
}
fn provides(&self) -> Option<Vec<Resource>> {
Some(vec![Resource::new(
"dir",
self.target.as_ref().to_str().unwrap(),
)])
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]

View file

@ -1,2 +1,4 @@
pub mod checkout;
pub mod submodules;
mod checkout;
//pub mod submodules;
pub use checkout::Checkout;

View file

@ -1,196 +0,0 @@
use std::error::Error;
use std::fmt;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct Hook<A, B>
where
A: Symbol,
B: Symbol,
{
a: A,
b: B,
}
// A and B are executed if either A or B are not reached
impl<A, B> Hook<A, B>
where
A: Symbol,
B: Symbol,
{
pub fn new(a: A, b: B) -> Self {
Self { a, b }
}
}
impl<A, B> Symbol for Hook<A, B>
where
A: Symbol,
B: Symbol,
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
self.a.target_reached().and_then(|reached| {
if reached {
self.b.target_reached()
} else {
Ok(reached)
}
})
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.a.execute()?;
self.b.execute()
}
fn get_prerequisites(&self) -> Vec<Resource> {
let mut r = vec![];
r.extend(self.a.get_prerequisites().into_iter());
r.extend(self.b.get_prerequisites().into_iter());
r
}
fn provides(&self) -> Option<Vec<Resource>> {
let mut r = vec![];
if let Some(provides) = self.a.provides() {
r.extend(provides.into_iter());
}
if let Some(provides) = self.b.provides() {
r.extend(provides.into_iter());
}
if r.is_empty() {
None
} else {
Some(r)
}
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<A, B> fmt::Display for Hook<A, B>
where
A: Symbol,
B: Symbol,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Hook {} and then {}", self.a, self.b)
}
}
#[cfg(test)]
mod test {
use std::error::Error;
use std::fmt;
use crate::symbols::hook::Hook;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
struct ErrSymbol(String);
impl Symbol for ErrSymbol {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Err(self.0.clone().into())
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
Err(self.0.clone().into())
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl fmt::Display for ErrSymbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "")
}
}
struct OkSymbol(bool);
impl Symbol for OkSymbol {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(self.0)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
Ok(())
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl fmt::Display for OkSymbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "")
}
}
#[test]
fn first_target_reached_fails() {
let res = Hook::new(ErrSymbol("first".into()), ErrSymbol("second".into())).target_reached();
assert_eq!(res.unwrap_err().description(), "first");
}
#[test]
fn first_target_not_reached() {
let res = Hook::new(OkSymbol(false), ErrSymbol("second".into())).target_reached();
assert_eq!(res.unwrap(), false);
}
#[test]
fn second_target_reached_fails() {
let res = Hook::new(OkSymbol(true), ErrSymbol("second".into())).target_reached();
assert_eq!(res.unwrap_err().description(), "second");
}
#[test]
fn second_target_not_reached() {
let res = Hook::new(OkSymbol(true), OkSymbol(false)).target_reached();
assert_eq!(res.unwrap(), false);
}
#[test]
fn everything_reached() {
let res = Hook::new(OkSymbol(true), OkSymbol(true)).target_reached();
assert_eq!(res.unwrap(), true);
}
#[test]
fn first_execute_fails() {
let res = Hook::new(ErrSymbol("first".into()), ErrSymbol("second".into())).execute();
assert_eq!(res.unwrap_err().description(), "first");
}
#[test]
fn second_execute_fails() {
let res = Hook::new(OkSymbol(true), ErrSymbol("second".into())).execute();
assert_eq!(res.unwrap_err().description(), "second");
}
#[test]
fn everything_executes() {
let res = Hook::new(OkSymbol(true), OkSymbol(true)).execute();
assert_eq!(res.unwrap(), ());
}
}

View file

@ -1,213 +0,0 @@
use std::error::Error;
use std::fmt;
use crate::resources::Resource;
use crate::symbols::{Action, Symbol, SymbolRunner};
pub struct List<'a> {
symbols: Vec<Box<dyn Symbol + 'a>>,
}
impl<'a> List<'a> {
pub fn new(symbols: Vec<Box<dyn Symbol + 'a>>) -> Self {
List { symbols }
}
}
impl Symbol for List<'_> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
for symbol in &self.symbols {
if !symbol.target_reached()? {
return Ok(false);
}
}
Ok(true)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
for symbol in &self.symbols {
symbol.execute()?;
}
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
let mut r = vec![];
for symbol in &self.symbols {
for req in symbol.get_prerequisites() {
if self.provides().map_or(true, |p| !p.contains(&req)) {
r.push(req)
}
}
}
r
}
fn provides(&self) -> Option<Vec<Resource>> {
let mut r = vec![];
for symbol in &self.symbols {
if let Some(provides) = symbol.provides() {
r.extend(provides.into_iter());
} else {
return None;
}
}
if r.is_empty() {
None
} else {
Some(r)
}
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolListAction::new(runner, &self.symbols))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(ListAction::new(
self
.symbols
.into_iter()
.map(|s| s.into_action(runner))
.collect(),
))
}
}
macro_rules! from_tuple {
( $($name:ident)* ) => (
#[allow(non_snake_case)]
impl<'a, $($name: 'a + Symbol,)*> From<($($name,)*)> for List<'a> {
fn from(($($name,)*): ($($name,)*)) -> Self {
Self::new(vec![$(Box::new($name),)*])
}
}
);
}
for_each_tuple!(from_tuple);
struct SymbolListAction<'a> {
runner: &'a dyn SymbolRunner,
symbols: &'a [Box<dyn Symbol + 'a>],
}
impl<'a> SymbolListAction<'a> {
fn new(runner: &'a dyn SymbolRunner, symbols: &'a [Box<dyn Symbol + 'a>]) -> Self {
Self { runner, symbols }
}
}
impl Action for SymbolListAction<'_> {
fn run(&self) -> Result<(), Box<dyn Error>> {
for symbol in self.symbols {
symbol.as_action(self.runner).run()?;
}
Ok(())
}
}
pub struct ListAction<'a> {
actions: Vec<Box<dyn Action + 'a>>,
}
impl<'a> ListAction<'a> {
pub fn new(actions: Vec<Box<dyn Action + 'a>>) -> Self {
Self { actions }
}
}
impl Action for ListAction<'_> {
fn run(&self) -> Result<(), Box<dyn Error>> {
for action in &self.actions {
action.run()?;
}
Ok(())
}
}
impl fmt::Display for List<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "List [ ")?;
for symbol in &self.symbols {
write!(f, "{} ", symbol)?;
}
write!(f, "]")
}
}
/*
#[cfg(test)]
mod test {
use std::error::Error;
use std::fmt;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use symbols::hook::List;
struct ErrSymbol(String);
impl Symbol for ErrSymbol {
fn target_reached(&self) -> Result<bool, Box<Error>> { Err(self.0.clone().into()) }
fn execute(&self) -> Result<(), Box<Error>> { Err(self.0.clone().into()) }
}
impl fmt::Display for ErrSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
struct OkSymbol(bool);
impl Symbol for OkSymbol {
fn target_reached(&self) -> Result<bool, Box<Error>> { Ok(self.0) }
fn execute(&self) -> Result<(), Box<Error>> { Ok(()) }
}
impl fmt::Display for OkSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
#[test]
fn first_target_reached_fails() {
let res = List::new(ErrSymbol("first".into()), ErrSymbol("second".into())).target_reached();
assert_eq!(res.unwrap_err().description(), "first");
}
#[test]
fn first_target_not_reached() {
let res = List::new(OkSymbol(false), ErrSymbol("second".into())).target_reached();
assert_eq!(res.unwrap(), false);
}
#[test]
fn second_target_reached_fails() {
let res = List::new(OkSymbol(true), ErrSymbol("second".into())).target_reached();
assert_eq!(res.unwrap_err().description(), "second");
}
#[test]
fn second_target_not_reached() {
let res = List::new(OkSymbol(true), OkSymbol(false)).target_reached();
assert_eq!(res.unwrap(), false);
}
#[test]
fn everything_reached() {
let res = List::new(OkSymbol(true), OkSymbol(true)).target_reached();
assert_eq!(res.unwrap(), true);
}
#[test]
fn first_execute_fails() {
let res = List::new(ErrSymbol("first".into()), ErrSymbol("second".into())).execute();
assert_eq!(res.unwrap_err().description(), "first");
}
#[test]
fn second_execute_fails() {
let res = List::new(OkSymbol(true), ErrSymbol("second".into())).execute();
assert_eq!(res.unwrap_err().description(), "second");
}
#[test]
fn everything_executes() {
let res = List::new(OkSymbol(true), OkSymbol(true)).execute();
assert_eq!(res.unwrap(), ());
}
}
*/

View file

@ -1,19 +1,18 @@
use std::error::Error;
use std::fmt;
use std::path::Path;
use crate::command_runner::CommandRunner;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use crate::storage::Storage;
use crate::symbols::Symbol;
use std::error::Error;
pub struct MariaDBDatabase<'a, D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> {
#[derive(Debug)]
pub struct Database<'a, D, S, C> {
db_name: D,
seed_file: S,
command_runner: &'a C,
}
impl<'a, D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> MariaDBDatabase<'a, D, S, C> {
impl<'a, D, S, C: CommandRunner> Database<'a, D, S, C> {
pub fn new(db_name: D, seed_file: S, command_runner: &'a C) -> Self {
MariaDBDatabase {
Self {
db_name,
seed_file,
command_runner,
@ -28,15 +27,7 @@ impl<'a, D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> MariaDBDatabase<'a, D,
}
}
impl<D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> fmt::Display
for MariaDBDatabase<'_, D, S, C>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MariaDB Database {}", self.db_name.as_ref())
}
}
impl<D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> Symbol for MariaDBDatabase<'_, D, S, C> {
impl<D: AsRef<str>, S: Storage, C: CommandRunner> Symbol for Database<'_, D, S, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(
self
@ -55,22 +46,11 @@ impl<D: AsRef<str>, S: AsRef<Path>, C: CommandRunner> Symbol for MariaDBDatabase
format!(
"mariadb '{}' < {}",
self.db_name.as_ref(),
self.seed_file.as_ref().to_str().unwrap()
self.seed_file.read_filename()?.to_str().unwrap()
),
],
)
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]

View file

@ -1,75 +0,0 @@
use std::error::Error;
use std::fmt;
use std::str::FromStr;
use crate::command_runner::CommandRunner;
use crate::storage::Storage;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct DatabaseDump<'a, N: AsRef<str>, C: CommandRunner, S: Storage> {
db_name: N,
storage: S,
command_runner: &'a C,
}
impl<'a, N: AsRef<str>, C: CommandRunner, S: Storage> DatabaseDump<'a, N, C, S> {
pub fn new(db_name: N, storage: S, command_runner: &'a C) -> Self {
DatabaseDump {
db_name,
storage,
command_runner,
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = self
.command_runner
.get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?;
Ok(String::from_utf8(b)?)
}
}
impl<N: AsRef<str>, C: CommandRunner, S: Storage> fmt::Display for DatabaseDump<'_, N, C, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Dump MariaDB Database {}", self.db_name.as_ref())
}
}
impl<N: AsRef<str>, C: CommandRunner, S: Storage> Symbol for DatabaseDump<'_, N, C, S> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let dump_date = self.storage.recent_date()?;
let modified_date = self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name.as_ref()))?;
if modified_date.trim_end() == "NULL" {
return Ok(false);
}
Ok(u64::from_str(modified_date.trim_end())? <= dump_date)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"sh",
args![
"-c",
format!(
"mysqldump '{}' > {}",
self.db_name.as_ref(),
self.storage.write_filename()
),
],
)
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]
mod test {}

View file

@ -0,0 +1,56 @@
use std::error::Error;
use std::str::FromStr;
use crate::command_runner::CommandRunner;
use crate::storage::Storage;
use crate::symbols::Symbol;
#[derive(Debug)]
pub struct Dump<'a, N, C, S> {
db_name: N,
storage: S,
command_runner: &'a C,
}
impl<'a, N, C: CommandRunner, S> Dump<'a, N, C, S> {
pub fn new(db_name: N, storage: S, command_runner: &'a C) -> Self {
Self {
db_name,
storage,
command_runner,
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = self
.command_runner
.get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?;
Ok(String::from_utf8(b)?)
}
}
impl<N: AsRef<str>, C: CommandRunner, S: Storage> Symbol for Dump<'_, N, C, S> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let dump_date = self.storage.recent_date()?;
let _modified_date = self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name.as_ref()))?;
let modified_date = _modified_date.trim_end();
Ok(modified_date != "NULL" && u64::from_str(modified_date)? <= dump_date)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"sh",
args![
"-c",
format!(
"mysqldump '{}' > {}",
self.db_name.as_ref(),
self.storage.write_filename().to_str().unwrap()
),
],
)
}
}
#[cfg(test)]
mod test {}

View file

@ -1,7 +1,7 @@
mod database;
mod database_dump;
mod dump;
mod user;
pub use self::database::MariaDBDatabase;
pub use self::database_dump::DatabaseDump;
pub use self::user::MariaDBUser;
pub use self::database::Database;
pub use self::dump::Dump;
pub use self::user::User;

View file

@ -1,18 +1,16 @@
use std::error::Error;
use std::fmt;
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use crate::symbols::Symbol;
use std::error::Error;
pub struct MariaDBUser<'a, U: AsRef<str>, C: CommandRunner> {
#[derive(Debug)]
pub struct User<'a, U, C> {
user_name: U,
command_runner: &'a C,
}
impl<'a, U: AsRef<str>, C: CommandRunner> MariaDBUser<'a, U, C> {
impl<'a, U: AsRef<str>, C: CommandRunner> User<'a, U, C> {
pub fn new(user_name: U, command_runner: &'a C) -> Self {
MariaDBUser {
Self {
user_name,
command_runner,
}
@ -26,13 +24,7 @@ impl<'a, U: AsRef<str>, C: CommandRunner> MariaDBUser<'a, U, C> {
}
}
impl<U: AsRef<str>, C: CommandRunner> fmt::Display for MariaDBUser<'_, U, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MariaDB User {}", self.user_name.as_ref())
}
}
impl<U: AsRef<str>, C: CommandRunner> Symbol for MariaDBUser<'_, U, C> {
impl<U: AsRef<str>, C: CommandRunner> Symbol for User<'_, U, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(
self
@ -52,21 +44,6 @@ impl<U: AsRef<str>, C: CommandRunner> Symbol for MariaDBUser<'_, U, C> {
))?;
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![Resource::new("user", self.user_name.as_ref())]
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]

View file

@ -1,88 +1,22 @@
use crate::resources::Resource;
use std::error::Error;
use std::fmt::Display;
pub trait Action {
fn run(&self) -> Result<(), Box<dyn Error>>;
}
pub trait SymbolRunner {
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>>;
}
impl<R: SymbolRunner + ?Sized> SymbolRunner for Box<R> {
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
(**self).run_symbol(symbol)
}
}
// Symbol
pub trait Symbol: Display {
pub trait Symbol {
fn target_reached(&self) -> Result<bool, Box<dyn Error>>;
fn execute(&self) -> Result<(), Box<dyn Error>>;
fn get_prerequisites(&self) -> Vec<Resource> {
vec![]
}
fn provides(&self) -> Option<Vec<Resource>> {
None
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>;
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a;
}
// SymbolAction
pub struct SymbolAction<'a, S: Symbol> {
runner: &'a dyn SymbolRunner,
symbol: &'a S,
}
impl<'a, S: Symbol> SymbolAction<'a, S> {
pub fn new(runner: &'a dyn SymbolRunner, symbol: &'a S) -> Self {
Self { runner, symbol }
}
}
impl<S: Symbol> Action for SymbolAction<'_, S> {
fn run(&self) -> Result<(), Box<dyn Error>> {
self.runner.run_symbol(self.symbol)
}
}
pub struct OwnedSymbolAction<'a, S: Symbol + 'a> {
runner: &'a dyn SymbolRunner,
symbol: S,
}
impl<'a, S: Symbol + 'a> OwnedSymbolAction<'a, S> {
pub fn new(runner: &'a dyn SymbolRunner, symbol: S) -> Self {
Self { runner, symbol }
}
}
impl<'a, S: Symbol + 'a> Action for OwnedSymbolAction<'a, S> {
fn run(&self) -> Result<(), Box<dyn Error>> {
self.runner.run_symbol(&self.symbol)
}
}
pub mod acme;
pub mod concat;
pub mod cron;
pub mod dir;
pub mod factory;
pub mod file;
pub mod git;
pub mod hook;
pub mod list;
pub mod mariadb;
pub mod nginx;
pub mod noop;
pub mod npm;
pub mod owner;
pub mod postgresql;
pub mod stored_directory;
pub mod saved_directory;
pub mod systemd;
pub mod tls;
pub mod user;

View file

@ -1 +0,0 @@
pub mod server;

View file

@ -1,246 +0,0 @@
use std::error::Error;
use std::fmt;
use std::io;
use std::path::{Path, PathBuf};
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::file::File as FileSymbol;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
#[derive(Debug)]
pub enum NginxServerError<E: Error> {
ExecError(E),
GenericError,
}
impl From<io::Error> for NginxServerError<io::Error> {
fn from(err: io::Error) -> Self {
Self::ExecError(err)
}
}
impl<E: Error> Error for NginxServerError<E> {
fn description(&self) -> &str {
match self {
Self::ExecError(ref e) => e.description(),
Self::GenericError => "Generic error",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
Self::ExecError(ref e) => Some(e),
_ => None,
}
}
}
impl<E: Error> fmt::Display for NginxServerError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.description())
}
}
pub struct NginxServer<'a, C: CommandRunner, T: AsRef<str>, P: AsRef<Path>> {
command_runner: &'a C,
file: FileSymbol<T, P>,
}
pub fn server_config(domain: &str, content: &str) -> String {
format!(
"server {{
listen 443 ssl http2;
server_name {0};
include \"snippets/acme-challenge.conf\";
ssl_certificate /etc/ssl/local_certs/{0}.chained.crt;
ssl_certificate_key /etc/ssl/private/{0}.key;
add_header Strict-Transport-Security \"max-age=31536000\";
{1}
}}
# Redirect all HTTP links to the matching HTTPS page
server {{
listen 80;
server_name {0};
include \"snippets/acme-challenge.conf\";
location / {{
return 301 https://$host$request_uri;
}}
}}
",
domain, content
)
}
pub fn php_server_config_snippet<SOCKET: AsRef<Path>, STATIC: AsRef<Path>>(
socket_path: SOCKET,
static_path: STATIC,
) -> String {
format!(
"
root {};
index index.html index.php;
location ~ [^/]\\.php(/|$) {{
fastcgi_pass unix:{};
include \"snippets/fastcgi-php.conf\";
}}",
static_path.as_ref().to_str().unwrap(),
socket_path.as_ref().to_str().unwrap()
)
}
pub trait SocketSpec {
fn to_nginx(&self) -> String;
}
impl<T> SocketSpec for T
where
T: AsRef<Path>,
{
fn to_nginx(&self) -> String {
format!("unix:{}:", self.as_ref().to_str().unwrap())
}
}
pub struct LocalTcpSocket(usize);
impl LocalTcpSocket {
pub const fn new(x: usize) -> Self {
Self(x)
}
}
impl SocketSpec for LocalTcpSocket {
fn to_nginx(&self) -> String {
format!("localhost:{}", self.0)
}
}
impl<'a, C: CommandRunner> NginxServer<'a, C, String, PathBuf> {
pub fn new_redir(domain: &'a str, target: &'a str, command_runner: &'a C) -> Self {
let content = server_config(
domain,
&format!(
"location / {{
return 301 $scheme://{}$request_uri;
}}",
target
),
);
Self::new(domain, content, command_runner)
}
pub fn new_proxy<S: SocketSpec, STATIC: AsRef<Path>>(
domain: &'a str,
socket_path: &S,
static_path: STATIC,
command_runner: &'a C,
) -> Self {
let proxy_content = format!(
"location / {{
try_files $uri @proxy;
}}
location @proxy {{
include fastcgi_params;
proxy_pass http://{};
proxy_redirect off;
}}",
socket_path.to_nginx()
);
let content = server_config(
domain,
&format!(
"
root {};
{}
",
static_path.as_ref().to_str().unwrap(),
proxy_content
),
);
Self::new(domain, content, command_runner)
}
pub fn new_php<SOCKET: AsRef<Path>, STATIC: AsRef<Path>>(
domain: &'a str,
socket_path: SOCKET,
static_path: STATIC,
command_runner: &'a C,
additional_config: &'a str,
) -> Self {
let content = server_config(
domain,
&(php_server_config_snippet(socket_path, static_path) + additional_config),
);
Self::new(domain, content, command_runner)
}
pub fn new_static<S: AsRef<Path>>(
domain: &'a str,
static_path: S,
command_runner: &'a C,
) -> Self {
let content = server_config(
domain,
&format!(
"
root {};
try_files $uri $uri/ $uri.html =404;
",
static_path.as_ref().to_str().unwrap()
),
);
Self::new(domain, content, command_runner)
}
pub fn new(domain: &'a str, content: String, command_runner: &'a C) -> Self {
let file_path: PathBuf = ["/etc/nginx/sites-enabled/", domain].iter().collect();
NginxServer {
command_runner,
file: FileSymbol::new(file_path, content),
}
}
}
impl<C: CommandRunner, T: AsRef<str>, P: AsRef<Path>> Symbol for NginxServer<'_, C, T, P> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.file.target_reached()? {
return Ok(false);
}
// TODO: Could try to find out if the server is in the live config
Ok(true)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.file.execute()?;
self
.command_runner
.run_successfully("systemctl", args!["reload-or-restart", "nginx"])
}
fn get_prerequisites(&self) -> Vec<Resource> {
self.file.get_prerequisites()
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<C: CommandRunner, T: AsRef<str>, P: AsRef<Path>> fmt::Display for NginxServer<'_, C, T, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Nginx server config")
}
}

View file

@ -1,31 +0,0 @@
use std::error::Error;
use std::fmt::{self, Display};
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct NoopSymbol;
impl Symbol for NoopSymbol {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(true)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
Ok(())
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl Display for NoopSymbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Noop")
}
}

View file

@ -1,25 +1,25 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use std::error::Error;
use std::fmt;
use std::path::Path;
use crate::command_runner::CommandRunner;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct NpmInstall<'a, T: AsRef<Path>, C: CommandRunner> {
#[derive(Debug)]
pub struct Install<'a, T: AsRef<Path>, C: CommandRunner> {
target: T,
command_runner: &'a C,
}
impl<'a, T: AsRef<Path>, C: CommandRunner> NpmInstall<'a, T, C> {
impl<'a, T: AsRef<Path>, C: CommandRunner> Install<'a, T, C> {
pub fn new(target: T, command_runner: &'a C) -> Self {
NpmInstall {
Install {
target,
command_runner,
}
}
}
impl<T: AsRef<Path>, C: CommandRunner> fmt::Display for NpmInstall<'_, T, C> {
impl<T: AsRef<Path>, C: CommandRunner> fmt::Display for Install<'_, T, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
@ -29,7 +29,7 @@ impl<T: AsRef<Path>, C: CommandRunner> fmt::Display for NpmInstall<'_, T, C> {
}
}
impl<T: AsRef<Path>, C: CommandRunner> Symbol for NpmInstall<'_, T, C> {
impl<T: AsRef<Path>, C: CommandRunner> Symbol for Install<'_, T, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.target.as_ref().exists() {
return Ok(false);
@ -61,17 +61,6 @@ impl<T: AsRef<Path>, C: CommandRunner> Symbol for NpmInstall<'_, T, C> {
],
)
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]

View file

@ -1,35 +1,37 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use std::borrow::Borrow;
use std::error::Error;
use std::fmt;
use std::fs;
use std::marker::PhantomData;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use users::get_user_by_name;
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct Owner<'a, C: CommandRunner, D: AsRef<Path>, U: AsRef<str>> {
path: D,
#[derive(Debug)]
pub struct Owner<_C, C, P, U> {
path: P,
user_name: U,
command_runner: &'a C,
command_runner: C,
phantom: PhantomData<_C>,
}
impl<'a, C: CommandRunner, D: AsRef<Path>, U: AsRef<str>> Owner<'a, C, D, U> {
pub fn new(path: D, user_name: U, command_runner: &'a C) -> Self {
Owner {
impl<_C, C, P, U> Owner<_C, C, P, U> {
pub fn new(path: P, user_name: U, command_runner: C) -> Self {
Self {
path,
user_name,
command_runner,
phantom: PhantomData::default(),
}
}
}
impl<C: CommandRunner, D: AsRef<Path>, U: AsRef<str>> Symbol for Owner<'_, C, D, U> {
impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, U: AsRef<str>> Symbol
for Owner<_C, C, P, U>
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !Path::new(self.path.as_ref()).exists() {
if !self.path.as_ref().exists() {
return Ok(false);
}
let actual_uid = fs::metadata(self.path.as_ref()).unwrap().uid();
@ -38,35 +40,9 @@ impl<C: CommandRunner, D: AsRef<Path>, U: AsRef<str>> Symbol for Owner<'_, C, D,
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
let uname: &str = self.user_name.as_ref();
self
.command_runner
.run_successfully("chown", args!["-R", uname, self.path.as_ref(),])
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![Resource::new("user", self.user_name.as_ref())]
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<C: CommandRunner, D: AsRef<Path>, U: AsRef<str>> fmt::Display for Owner<'_, C, D, U> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"Owner {} for {}",
self.user_name.as_ref(),
self.path.as_ref().display()
self.command_runner.borrow().run_successfully(
"chown",
args!["-R", self.user_name.as_ref(), self.path.as_ref()],
)
}
}

View file

@ -1,9 +1,8 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use std::error::Error;
use std::fmt;
use crate::command_runner::CommandRunner;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct PostgreSQLDatabase<'a, N: AsRef<str>, S: AsRef<str>, C: CommandRunner> {
name: N,
seed_file: S,
@ -85,17 +84,6 @@ impl<N: AsRef<str>, S: AsRef<str>, C: CommandRunner> Symbol for PostgreSQLDataba
],
)
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]

View file

@ -1,57 +1,50 @@
use crate::command_runner::CommandRunner;
use crate::storage::Storage;
use crate::symbols::Symbol;
use std::borrow::Borrow;
use std::error::Error;
use std::fmt;
use std::fs;
use std::io;
use std::marker::PhantomData;
use std::path::Path;
use std::str::FromStr;
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::storage::Storage;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
#[derive(Debug, PartialEq)]
pub enum StorageDirection {
Load,
Save,
Store,
}
pub struct StoredDirectory<'a, P: AsRef<Path>, S: Storage, C: CommandRunner> {
#[derive(Debug)]
pub struct SavedDirectory<_C, C, P, S> {
path: P,
storage: S,
dir: StorageDirection,
command_runner: &'a C,
command_runner: C,
phantom: PhantomData<_C>,
}
impl<'a, P: AsRef<Path>, S: Storage, C: CommandRunner> StoredDirectory<'a, P, S, C> {
pub fn new(path: P, storage: S, dir: StorageDirection, command_runner: &'a C) -> Self {
StoredDirectory {
impl<_C, C, P, S> SavedDirectory<_C, C, P, S> {
pub fn new(path: P, storage: S, dir: StorageDirection, command_runner: C) -> Self {
Self {
path,
storage,
dir,
command_runner,
phantom: PhantomData::default(),
}
}
}
impl<P: AsRef<Path>, S: Storage, C: CommandRunner> fmt::Display for StoredDirectory<'_, P, S, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Stored directory {} ({:?})",
self.path.as_ref().display(),
self.dir
)
}
}
impl<P: AsRef<Path>, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_, P, S, C> {
impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S: Storage> Symbol
for SavedDirectory<_C, C, P, S>
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let metadata = fs::metadata(self.path.as_ref());
// Check if dir exists
if let Err(e) = metadata {
return if e.kind() == io::ErrorKind::NotFound {
Ok(self.dir == StorageDirection::Save)
Ok(self.dir == StorageDirection::Store)
} else {
Err(Box::new(e))
};
@ -64,7 +57,7 @@ impl<P: AsRef<Path>, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_
}
let dump_date = self.storage.recent_date()?;
let output = self.command_runner.get_output(
let output = self.command_runner.borrow().get_output(
"sh",
args![
"-c",
@ -75,12 +68,12 @@ impl<P: AsRef<Path>, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_
],
)?;
let modified_date = u64::from_str(String::from_utf8(output)?.trim_end())?;
if if self.dir == StorageDirection::Save {
if if self.dir == StorageDirection::Store {
modified_date > dump_date
} else {
dump_date > modified_date
} {
let output = self.command_runner.run_with_args(
let output = self.command_runner.borrow().run_with_args(
"diff",
args!["-rq", self.storage.read_filename()?, self.path.as_ref()],
)?;
@ -98,51 +91,19 @@ impl<P: AsRef<Path>, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_
if self.dir == StorageDirection::Load {
self
.command_runner
.borrow()
.run_successfully("rm", args!["-rf", self.path.as_ref()])?;
self.command_runner.run_successfully(
self.command_runner.borrow().run_successfully(
"cp",
args!["-a", self.storage.read_filename()?, self.path.as_ref()],
)
} else {
self.command_runner.run_successfully(
self.command_runner.borrow().run_successfully(
"cp",
args!["-a", self.path.as_ref(), self.storage.write_filename()],
)
}
}
fn get_prerequisites(&self) -> Vec<Resource> {
if self.dir == StorageDirection::Save {
return vec![];
}
if let Some(parent) = self.path.as_ref().parent() {
vec![Resource::new("dir", parent.to_str().unwrap())]
} else {
vec![]
}
}
fn provides(&self) -> Option<Vec<Resource>> {
if self.dir == StorageDirection::Load {
Some(vec![Resource::new(
"dir",
self.path.as_ref().to_str().unwrap(),
)])
} else {
None
}
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]

View file

@ -1,3 +1,7 @@
pub mod reload;
pub mod user_service;
pub mod user_session;
mod reload;
mod user_service;
mod user_session;
pub use reload::ReloadService;
pub use user_service::UserService;
pub use user_session::UserSession;

View file

@ -1,49 +1,35 @@
use std::error::Error;
use std::fmt;
use crate::command_runner::CommandRunner;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use crate::symbols::Symbol;
use std::borrow::Borrow;
use std::error::Error;
use std::marker::PhantomData;
pub struct ReloadService<'a, S, C: CommandRunner> {
#[derive(Debug)]
pub struct ReloadService<_C, C, S> {
service: S,
command_runner: &'a C,
command_runner: C,
phantom: PhantomData<_C>,
}
impl<'a, S, C: CommandRunner> ReloadService<'a, S, C> {
pub fn new(service: S, command_runner: &'a C) -> Self {
ReloadService {
impl<_C, C, S> ReloadService<_C, C, S> {
pub fn new(command_runner: C, service: S) -> Self {
Self {
service,
command_runner,
phantom: PhantomData::default(),
}
}
}
impl<S: AsRef<str>, C: CommandRunner> Symbol for ReloadService<'_, S, C> {
impl<S: AsRef<str>, _C: CommandRunner, C: Borrow<_C>> Symbol for ReloadService<_C, C, S> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(true)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
self.command_runner.borrow().run_successfully(
"systemctl",
args!["reload-or-restart", self.service.as_ref()],
)
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<S: AsRef<str>, C: CommandRunner> fmt::Display for ReloadService<'_, S, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Reload service {}", self.service.as_ref())
}
}

View file

@ -1,132 +1,34 @@
use crate::command_runner::{CommandRunner, SetuidCommandRunner};
use crate::symbols::Symbol;
use std::error::Error;
use std::ffi::OsStr;
use std::fmt;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::process::Output;
use std::thread::sleep;
use std::time::Duration;
use crate::command_runner::{CommandRunner, SetuidCommandRunner};
use crate::resources::Resource;
use crate::symbols::file::File as FileSymbol;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
#[derive(Debug)]
pub enum UserServiceError<E: Error> {
ActivationFailed(io::Result<Output>),
ExecError(E),
GenericError,
}
impl From<io::Error> for UserServiceError<io::Error> {
fn from(err: io::Error) -> Self {
Self::ExecError(err)
}
}
impl<E: Error> Error for UserServiceError<E> {
fn description(&self) -> &str {
match self {
Self::ExecError(ref e) => e.description(),
Self::GenericError => "Generic error",
Self::ActivationFailed(_) => "Activation of service failed",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
Self::ExecError(ref e) => Some(e),
_ => None,
}
}
}
impl<E: Error> fmt::Display for UserServiceError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.description())?;
if let Self::ActivationFailed(Ok(ref log)) = self {
write!(f, ": {:?}", log)?;
};
Ok(())
}
}
pub struct UserService<'a, S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> {
pub struct UserService<'a, S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> {
socket_path: S,
service_name: &'a str,
user_name: U,
command_runner: R,
config: FileSymbol<C, PathBuf>,
command_runner: SetuidCommandRunner<'a, U, R>,
}
impl<'a, S: AsRef<Path>, U: AsRef<str> + Clone, R: CommandRunner>
UserService<'a, S, U, String, SetuidCommandRunner<'a, U, R>>
{
pub fn new_nodejs(
home: &'_ Path,
user_name: U,
service_name: &'a str,
path: &'_ Path,
command_runner: &'a R,
socket_path: S,
) -> Self {
let content = format!(
"[Service]
Environment=NODE_ENV=production
Environment=PORT={1}
ExecStartPre=/bin/rm -f {1}
ExecStart=/usr/bin/nodejs {0}
ExecStartPost=/bin/sh -c 'sleep 1 && chmod 666 {1}'
# FIXME: This only works if the nodejs path is a directory
WorkingDirectory={0}
#RuntimeDirectory=service
#RuntimeDirectoryMode=766
Restart=always
[Install]
WantedBy=default.target
",
path.to_str().unwrap(),
socket_path.as_ref().to_str().unwrap()
);
Self::new(
socket_path,
home,
user_name,
service_name,
command_runner,
content,
)
}
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'static, S, U, R> {
pub fn new(
socket_path: S,
home: &'_ Path,
user_name: U,
service_name: &'a str,
command_runner: &'a R,
content: String,
service_name: &'static str,
command_runner: &'static R,
) -> Self {
let config_path: PathBuf = [
home,
format!(".config/systemd/user/{}.service", service_name).as_ref(),
]
.iter()
.collect();
UserService {
Self {
socket_path,
service_name,
user_name: user_name.clone(),
command_runner: SetuidCommandRunner::new(user_name, command_runner),
config: FileSymbol::new(config_path, content),
}
}
}
impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> UserService<'_, S, U, C, R> {
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'_, S, U, R> {
fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result<String, Box<dyn Error>> {
let mut tries = 5;
loop {
@ -161,12 +63,13 @@ impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> UserService
"ActiveState=activating" => sleep(Duration::from_millis(500)),
"ActiveState=active" => return Ok(true),
"ActiveState=failed" => {
return Err(Box::new(
UserServiceError::ActivationFailed(self.command_runner.run_with_args(
return Err(
String::from_utf8(self.command_runner.get_output(
"journalctl",
args!["--user", format!("--user-unit={}", self.service_name)],
)) as UserServiceError<io::Error>,
))
)?)?
.into(),
)
}
_ => return Ok(false),
}
@ -174,26 +77,18 @@ impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> UserService
}
}
impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> Symbol
for UserService<'_, S, U, C, R>
{
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> Symbol for UserService<'_, S, U, R> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !(self.config.target_reached()?) {
return Ok(false);
}
self.check_if_service()
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.config.execute()?;
self.systemctl_wait_for_dbus(args!["--user", "enable", self.service_name])?;
self.systemctl_wait_for_dbus(args!["--user", "restart", self.service_name])?;
loop {
if !(self.check_if_service()?) {
return Err(Box::new(
UserServiceError::GenericError as UserServiceError<io::Error>,
));
return Err("Generic error".into());
}
if self.socket_path.as_ref().exists() {
@ -202,32 +97,4 @@ impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> Symbol
sleep(Duration::from_millis(500));
}
}
fn get_prerequisites(&self) -> Vec<Resource> {
let mut r = vec![Resource::new(
"file",
format!("/var/lib/systemd/linger/{}", self.user_name.as_ref()),
)];
r.extend(self.config.get_prerequisites().into_iter());
r
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<S: AsRef<Path>, U: AsRef<str>, C: AsRef<str>, R: CommandRunner> fmt::Display
for UserService<'_, S, U, C, R>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Systemd user service unit for {}", self.service_name)
}
}

View file

@ -1,55 +1,26 @@
use std::error::Error;
use std::fmt;
use std::path::PathBuf;
use crate::command_runner::CommandRunner;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use crate::symbols::Symbol;
use std::error::Error;
use std::path::Path;
#[derive(Debug)]
pub enum SystemdUserSessionError<E: Error> {
ExecError(E),
GenericError,
}
impl<E: Error> Error for SystemdUserSessionError<E> {
fn description(&self) -> &str {
match self {
Self::ExecError(ref e) => e.description(),
Self::GenericError => "Generic error",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
Self::ExecError(ref e) => Some(e),
_ => None,
}
}
}
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, U: AsRef<str>, C: CommandRunner> {
pub struct UserSession<'a, U, C> {
user_name: U,
command_runner: &'a C,
}
impl<'a, U: AsRef<str>, C: CommandRunner> SystemdUserSession<'a, U, C> {
impl<'a, U, C> UserSession<'a, U, C> {
pub fn new(user_name: U, command_runner: &'a C) -> Self {
SystemdUserSession {
Self {
user_name,
command_runner,
}
}
}
impl<U: AsRef<str>, C: CommandRunner> Symbol for SystemdUserSession<'_, U, C> {
impl<U: AsRef<str>, C: CommandRunner> Symbol for UserSession<'_, U, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let mut path = PathBuf::from("/var/lib/systemd/linger");
path.push(self.user_name.as_ref());
let path = Path::new("/var/lib/systemd/linger").join(self.user_name.as_ref());
Ok(path.exists())
// Could also do `loginctl show-user ${self.user_name} | grep -F 'Linger=yes`
}
@ -59,21 +30,4 @@ impl<U: AsRef<str>, C: CommandRunner> Symbol for SystemdUserSession<'_, U, C> {
.command_runner
.run_successfully("loginctl", args!["enable-linger", self.user_name.as_ref()])
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<U: AsRef<str>, C: CommandRunner> fmt::Display for SystemdUserSession<'_, U, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Systemd user session for {}", self.user_name.as_ref())
}
}

View file

@ -1,56 +1,39 @@
use std::borrow::Cow;
use std::error::Error;
use std::ffi::OsStr;
use std::fmt;
use std::path::PathBuf;
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use crate::symbols::Symbol;
use std::borrow::Borrow;
use std::error::Error;
use std::path::Path;
pub struct TlsCsr<'a, C: CommandRunner> {
domain: Cow<'a, str>,
command_runner: &'a C,
#[derive(Debug)]
pub struct Csr<C, D, K, P> {
command_runner: C,
domain: D,
key_path: K,
csr_path: P,
}
impl<'a, C: CommandRunner> TlsCsr<'a, C> {
pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self {
TlsCsr {
domain,
impl<C, D, K, P> Csr<C, D, K, P> {
pub fn new(command_runner: C, domain: D, key_path: K, csr_path: P) -> Self {
Self {
command_runner,
domain,
key_path,
csr_path,
}
}
fn get_key_path(&self) -> PathBuf {
format!("/etc/ssl/private/{}.key", self.domain).into()
}
fn get_csr_path(&self) -> PathBuf {
format!("/etc/ssl/local_certs/{}.csr", self.domain).into()
}
}
impl<C: CommandRunner> fmt::Display for TlsCsr<'_, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TlsCsr {}", self.domain)
}
}
impl<C: CommandRunner> Symbol for TlsCsr<'_, C> {
impl<C: CommandRunner, D: Borrow<str>, K: Borrow<Path>, P: Borrow<Path>> Symbol
for Csr<C, D, K, P>
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.get_csr_path().exists() {
if !self.csr_path.borrow().exists() {
return Ok(false);
}
let output = self.command_runner.get_stderr(
"openssl",
&[
OsStr::new("req"),
"-in".as_ref(),
self.get_csr_path().as_ref(),
"-noout".as_ref(),
"-verify".as_ref(),
],
args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",],
)?;
Ok(output == b"verify OK\n")
}
@ -58,35 +41,20 @@ impl<C: CommandRunner> Symbol for TlsCsr<'_, C> {
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"openssl",
&[
OsStr::new("req"),
"-new".as_ref(),
"-sha256".as_ref(),
"-key".as_ref(),
self.get_key_path().as_ref(),
"-out".as_ref(),
self.get_csr_path().as_ref(),
"-subj".as_ref(),
format!("/CN={}", self.domain).as_ref(),
args![
"req",
"-new",
"-sha256",
"-key",
self.key_path.borrow(),
"-out",
self.csr_path.borrow(),
"-subj",
format!("/CN={}", self.domain.borrow()),
],
)?;
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![Resource::new("file", self.get_key_path().to_str().unwrap())]
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]

View file

@ -1,57 +1,42 @@
use std::borrow::Cow;
use std::error::Error;
use std::ffi::OsStr;
use std::fmt;
use std::path::PathBuf;
use crate::command_runner::CommandRunner;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use crate::symbols::Symbol;
use std::error::Error;
use std::path::Path;
pub struct TlsKey<'a, C: CommandRunner> {
domain: Cow<'a, str>,
command_runner: &'a C,
#[derive(Debug)]
pub struct Key<C, P> {
file_path: P,
command_runner: C,
}
impl<'a, C: CommandRunner> TlsKey<'a, C> {
pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self {
TlsKey {
domain,
impl<C, P> Key<C, P> {
pub fn new(command_runner: C, file_path: P) -> Self {
Self {
file_path,
command_runner,
}
}
fn get_path(&self) -> PathBuf {
["/etc/ssl/private", &format!("{}.key", self.domain)]
.iter()
.collect()
}
fn get_bytes(&self) -> u32 {
4096
}
}
impl<C: CommandRunner> fmt::Display for TlsKey<'_, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TlsKey {}", self.domain)
}
}
impl<C: CommandRunner> Symbol for TlsKey<'_, C> {
impl<C: CommandRunner, P: AsRef<Path>> Symbol for Key<C, P> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.get_path().exists() {
if !self.file_path.as_ref().exists() {
return Ok(false);
}
let stdout = self.command_runner.get_output(
"openssl",
&[
OsStr::new("rsa"),
"-in".as_ref(),
self.get_path().as_ref(),
"-noout".as_ref(),
"-check".as_ref(),
"-text".as_ref(),
args![
"rsa",
"-in",
self.file_path.as_ref(),
"-noout",
"-check",
"-text",
],
)?;
// FIXME check bytes
@ -61,25 +46,14 @@ impl<C: CommandRunner> Symbol for TlsKey<'_, C> {
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"openssl",
&[
OsStr::new("genrsa"),
"-out".as_ref(),
self.get_path().as_ref(),
self.get_bytes().to_string().as_ref(),
args![
"genrsa",
"-out",
self.file_path.as_ref(),
self.get_bytes().to_string(),
],
)
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]

View file

@ -1,7 +1,5 @@
mod csr;
mod key;
mod self_signed_cert;
pub use self::csr::TlsCsr;
pub use self::key::TlsKey;
pub use self::self_signed_cert::SelfSignedTlsCert;
pub use self::csr::Csr;
pub use self::key::Key;

View file

@ -1,119 +0,0 @@
use std::error::Error;
use std::fmt;
use std::path::PathBuf;
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct SelfSignedTlsCert<'a, D: AsRef<str>, C: CommandRunner> {
domain: D,
command_runner: &'a C,
}
impl<'a, D: AsRef<str>, C: CommandRunner> SelfSignedTlsCert<'a, D, C> {
pub fn new(domain: D, command_runner: &'a C) -> Self {
SelfSignedTlsCert {
domain,
command_runner,
}
}
fn get_key_path(&self) -> PathBuf {
format!("/etc/ssl/private/{}.key", self.domain.as_ref()).into()
}
fn get_cert_path(&self) -> PathBuf {
format!("/etc/ssl/local_certs/{}.chained.crt", self.domain.as_ref()).into()
}
}
impl<D: AsRef<str>, C: CommandRunner> fmt::Display for SelfSignedTlsCert<'_, D, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SelfSignedTlsCert {}", self.domain.as_ref())
}
}
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
impl<D: AsRef<str>, C: CommandRunner> Symbol for SelfSignedTlsCert<'_, D, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.get_cert_path().exists() {
return Ok(false);
}
let output = self.command_runner.run_with_args(
"openssl",
args![
"x509",
"-in",
self.get_cert_path(),
"-noout",
"-subject",
"-checkend",
(30 * DAYS_IN_SECONDS).to_string(),
],
)?;
println!("{}", output.status.code().unwrap());
match output.status.code() {
Some(0) => Ok(
output.stdout
== format!(
"subject=CN = {}\nCertificate will not expire\n",
self.domain.as_ref()
)
.as_bytes(),
),
Some(_) => {
if output.stdout
== format!(
"subject=CN = {}\nCertificate will expire\n",
self.domain.as_ref()
)
.as_bytes()
{
Ok(false)
} else {
Err("Exit code non-zero, but wrong stdout".to_string().into())
}
}
_ => Err("Apparently killed by signal".to_string().into()),
}
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"openssl",
args![
"req",
"-x509",
"-sha256",
"-days",
"90",
"-key",
self.get_key_path(),
"-out",
self.get_cert_path(),
"-subj",
format!("/CN={}", self.domain.as_ref()),
],
)
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![Resource::new("file", self.get_key_path().to_str().unwrap())]
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]
mod test {}

View file

@ -1,97 +1,23 @@
use std::borrow::Cow;
use std::error::Error;
use std::fmt;
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use crate::symbols::Symbol;
use std::error::Error;
#[derive(Debug)]
pub enum UserAdderError {
AlreadyExists,
UnknownError,
ImplError(Box<dyn Error>),
pub struct User<U, C> {
user_name: U,
command_runner: C,
}
impl Error for UserAdderError {
fn description(&self) -> &str {
match self {
Self::AlreadyExists => "User already exists",
Self::UnknownError => "Unknown error",
Self::ImplError(_) => "User adding error",
}
}
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::ImplError(ref e) => Some(e.as_ref()),
_ => None,
}
}
}
impl fmt::Display for UserAdderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.source() {
Some(e) => write!(f, "{} (cause: {})", self.description(), e),
None => write!(f, "{}", self.description()),
}
}
}
pub trait UserAdder {
fn add_user(&self, user_name: &str) -> Result<(), UserAdderError>;
}
#[derive(Debug, PartialEq)]
pub enum UserError {
GenericError,
}
impl Error for UserError {
fn description(&self) -> &str {
match self {
Self::GenericError => "Could not find out if user exists",
}
}
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
_ => None,
}
}
}
impl fmt::Display for UserError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.source() {
Some(e) => write!(f, "{} (cause: {})", self.description(), e),
None => write!(f, "{}", self.description()),
}
}
}
pub struct User<'a, C: CommandRunner, A: UserAdder> {
user_name: Cow<'a, str>,
command_runner: &'a C,
user_adder: &'a A,
}
impl<'a, C: CommandRunner, A: 'a + UserAdder> User<'a, C, A> {
pub fn new(user_name: Cow<'a, str>, command_runner: &'a C, user_adder: &'a A) -> Self {
User {
impl<U, C> User<U, C> {
pub fn new(user_name: U, command_runner: C) -> Self {
Self {
user_name,
command_runner,
user_adder,
}
}
}
impl<'a, C: CommandRunner, A: 'a + UserAdder> fmt::Display for User<'a, C, A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "User {}", self.user_name)
}
}
impl<'a, C: CommandRunner, A: 'a + UserAdder> Symbol for User<'a, C, A> {
impl<U: AsRef<str>, C: CommandRunner> Symbol for User<U, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let output = self
.command_runner
@ -99,108 +25,34 @@ impl<'a, C: CommandRunner, A: 'a + UserAdder> Symbol for User<'a, C, A> {
match output.status.code() {
Some(2) => Ok(false),
Some(0) => Ok(true),
_ => Err(Box::new(UserError::GenericError)),
_ => Err("Unknown error".into()),
}
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.user_adder
.add_user(self.user_name.as_ref())
.map_err(|e| Box::new(e) as Box<dyn Error>)
}
fn provides(&self) -> Option<Vec<Resource>> {
Some(vec![Resource::new("user", self.user_name.to_owned())])
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
pub struct SystemUserAdder<'a, C: CommandRunner> {
command_runner: &'a C,
}
impl<'a, C: CommandRunner> SystemUserAdder<'a, C> {
pub fn new(command_runner: &'a C) -> Self {
SystemUserAdder { command_runner }
}
}
impl<C: CommandRunner> UserAdder for SystemUserAdder<'_, C> {
fn add_user(&self, user_name: &str) -> Result<(), UserAdderError> {
let output = self.command_runner.run_with_args(
self.command_runner.run_successfully(
"adduser",
args![
// "-m", // Necessary for Fedora, not accepted in Debian
"--system", user_name,
"--system",
self.user_name.as_ref(),
],
);
match output {
Ok(output) => match output.status.code() {
Some(0) => Ok(()),
Some(1) => {
println!("{:?}", output);
Err(UserAdderError::AlreadyExists)
}
_ => {
println!("{:?}", output);
Err(UserAdderError::UnknownError)
}
},
Err(e) => Err(UserAdderError::ImplError(Box::new(e))),
}
)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use std::error::Error;
use std::fmt;
use crate::command_runner::StdCommandRunner;
use crate::symbols::user::User;
use crate::symbols::user::UserAdder;
use crate::symbols::user::UserAdderError;
use crate::symbols::Symbol;
#[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 {
fn add_user(&self, _user_name: &str) -> Result<(), UserAdderError> {
Ok(())
}
}
#[test]
fn test_target_reached_nonexisting() {
let symbol = User {
user_name: "nonexisting".into(),
command_runner: &StdCommandRunner,
user_adder: &DummyUserAdder,
user_name: "nonexisting",
command_runner: StdCommandRunner,
};
assert_eq!(symbol.target_reached().unwrap(), false);
}
@ -208,9 +60,8 @@ mod test {
#[test]
fn test_target_reached_root() {
let symbol = User {
user_name: "root".into(),
command_runner: &StdCommandRunner,
user_adder: &DummyUserAdder,
user_name: "root",
command_runner: StdCommandRunner,
};
assert_eq!(symbol.target_reached().unwrap(), true);
}

View file

@ -1,2 +1,5 @@
pub mod plugin;
pub mod translation;
mod plugin;
mod translation;
pub use plugin::Plugin;
pub use translation::Translation;

View file

@ -1,24 +1,23 @@
use regex::Regex;
use std::error::Error;
use std::fmt;
use std::fs::File as FsFile;
use std::io;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use crate::symbols::Symbol;
pub struct WordpressPlugin<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> {
#[derive(Debug)]
pub struct Plugin<'a, P, N, R> {
base: P,
name: N,
command_runner: &'a R,
}
impl<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> WordpressPlugin<'a, P, N, R> {
impl<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Plugin<'a, P, N, R> {
pub fn new(base: P, name: N, command_runner: &'a R) -> Self {
WordpressPlugin {
Self {
base,
name,
command_runner,
@ -34,7 +33,7 @@ impl<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> WordpressPlugin<'a, P,
}
}
impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Symbol for WordpressPlugin<'_, P, N, R> {
impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Symbol for Plugin<'_, P, N, R> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let base_path = self.get_path();
if !base_path.exists() {
@ -99,30 +98,4 @@ impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Symbol for WordpressPlugin
args![zip, "-d", self.base.as_ref().join("wp-content/plugins")],
)
}
fn get_prerequisites(&self) -> Vec<Resource> {
match self.get_path().parent() {
Some(p) => vec![Resource::new("dir", p.to_string_lossy())],
None => vec![],
}
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> fmt::Display
for WordpressPlugin<'_, P, N, R>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "WordpressPlugin {}", self.name.as_ref())
}
}

View file

@ -1,31 +1,26 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use regex::Regex;
use std::cmp::max;
use std::error::Error;
use std::fmt;
use std::fs::File as FsFile;
use std::io;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::path::PathBuf;
use crate::command_runner::CommandRunner;
use crate::resources::Resource;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct WordpressTranslation<'a, C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> {
#[derive(Debug)]
pub struct Translation<'a, C, D, R> {
path: D,
version: &'a str,
locale: C,
command_runner: &'a R,
}
impl<'a, C: AsRef<str>, R: CommandRunner> WordpressTranslation<'a, C, PathBuf, R> {
pub fn new<D: AsRef<Path>>(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self {
WordpressTranslation {
path: [path.as_ref(), "wp-content/languages".as_ref()]
.iter()
.collect(),
impl<'a, D, C: AsRef<str>, R: CommandRunner> Translation<'a, C, D, R> {
pub fn new(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self {
Self {
path,
version,
locale,
command_runner,
@ -33,7 +28,7 @@ impl<'a, C: AsRef<str>, R: CommandRunner> WordpressTranslation<'a, C, PathBuf, R
}
}
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> WordpressTranslation<'_, C, D, R> {
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Translation<'_, C, D, R> {
fn get_pairs(&self) -> Vec<(String, PathBuf)> {
let version_x = self
.version
@ -64,7 +59,7 @@ impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> WordpressTranslation<'_, C
}
}
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Symbol for WordpressTranslation<'_, C, D, R> {
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Symbol for Translation<'_, C, D, R> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let mut newest = String::new();
let match_date = Regex::new("(?m)^\"PO-Revision-Date: (.+)\\+0000\\\\n\"$").unwrap();
@ -115,27 +110,4 @@ impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Symbol for WordpressTransl
}
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![Resource::new("dir", self.path.as_ref().to_str().unwrap())]
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> fmt::Display
for WordpressTranslation<'_, C, D, R>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "WordpressTranslation {}", self.path.as_ref().display())
}
}