diff --git a/Cargo.toml b/Cargo.toml index 8000d82..94b28bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ name = "schematics" version = "0.1.0" authors = ["Adrian Heine "] edition = "2018" -build = "src/build.rs" [dependencies] users = "0.7.0" diff --git a/src/artifacts/mod.rs b/src/artifacts/mod.rs deleted file mode 100644 index c02538b..0000000 --- a/src/artifacts/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::path::{Path as ActualPath, PathBuf}; - -#[derive(Clone, Debug)] -pub struct Path(pub PathBuf); -impl AsRef for Path { - fn as_ref(&self) -> &ActualPath { - &self.0 - } -} - -#[derive(Clone, Debug)] -pub struct UserName(pub String); - -#[derive(Clone, Debug)] -pub struct ServiceName(pub String); - -#[derive(Clone, Debug)] -pub struct DatabaseName(pub String); diff --git a/src/build.rs b/src/build.rs index 180cce0..492cfad 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,6 +1,5 @@ use std::env; use std::fs::{read_dir, File}; -use std::io::ErrorKind::NotFound; use std::io::Read; use std::io::Write; use std::path::{Path, PathBuf}; @@ -21,38 +20,23 @@ pub fn create_static_output_files(source_dir: &str) { let out_dir = env::var("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("static_files.rs"); let mut f = File::create(&dest_path).unwrap(); - match read_dir(source_dir) { - Ok(dir_content) => { - for maybe_dir_entry in dir_content { - let file_path = maybe_dir_entry.unwrap().path(); - let mut buffer = String::new(); - File::open(file_path.clone()) - .unwrap() - .read_to_string(&mut buffer) - .unwrap(); - let fence = buffer.chars().filter(|c| *c == '#').collect::() + "#"; - f.write_all( - format!( - "pub const {}: &str = r{1}\"{2}\"{1};\n", - get_const_name(&file_path), - fence, - buffer - ) - .as_bytes(), - ) - .unwrap(); - } - } - Err(err) => { - if err.kind() == NotFound { - } else { - Err(err).unwrap() - } - } + for maybe_dir_entry in read_dir(source_dir).unwrap() { + let file_path = maybe_dir_entry.unwrap().path(); + let mut buffer = String::new(); + File::open(file_path.clone()) + .unwrap() + .read_to_string(&mut buffer) + .unwrap(); + let fence = buffer.chars().filter(|c| *c == '#').collect::() + "#"; + f.write_all( + format!( + "pub const {}: &str = r{1}\"{2}\"{1};\n", + get_const_name(&file_path), + fence, + buffer + ) + .as_bytes(), + ) + .unwrap(); } } - -#[allow(unused)] -fn main() { - create_static_output_files("static_files"); -} diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index f367c04..0000000 --- a/src/builder.rs +++ /dev/null @@ -1,767 +0,0 @@ -use crate::command_runner::{SetuidCommandRunner, StdCommandRunner}; -use crate::resources::{ - AcmeAccountKey, AcmeChallengesDir, AcmeChallengesNginxSnippet, AcmeRootCert, AcmeUser, Cert, - CertChain, Cron, Csr, DefaultServer, Dir, File, GitCheckout, Key, KeyAndCertBundle, - LoadedDirectory, MariaDbDatabase, MariaDbUser, NpmInstall, Owner, PhpFpmPool, Resource, - ServeCustom, ServePhp, ServeRedir, ServeService, ServeStatic, StoredDirectory, - SystemdSocketService, User, UserForDomain, WordpressPlugin, WordpressTranslation, -}; -use crate::static_files::LETS_ENCRYPT_X3_CROSS_SIGNED; -use crate::storage::SimpleStorage; -use crate::symbols::acme::Cert as CertSymbol; -use crate::symbols::concat::Concat as ConcatSymbol; -use crate::symbols::cron::Cron as CronSymbol; -use crate::symbols::dir::Dir as DirSymbol; -use crate::symbols::file::File as FileSymbol; -use crate::symbols::git::Checkout as GitCheckoutSymbol; -use crate::symbols::mariadb::{ - Database as MariaDbDatabaseSymbol, Dump as MariaDbDumpSymbol, User as MariaDbUserSymbol, -}; -use crate::symbols::npm::Install as NpmInstallSymbol; -use crate::symbols::owner::Owner as OwnerSymbol; -use crate::symbols::saved_directory::{SavedDirectory as SavedDirectorySymbol, StorageDirection}; -use crate::symbols::systemd::{ - ReloadService as ReloadServiceSymbol, UserService as UserServiceSymbol, - UserSession as SystemdUserSessionSymbol, -}; -use crate::symbols::tls::Csr as CsrSymbol; -use crate::symbols::tls::Key as KeySymbol; -use crate::symbols::user::User as UserSymbol; -use crate::symbols::wordpress::{ - Plugin as WordpressPluginSymbol, Translation as WordpressTranslationSymbol, -}; -use crate::templates::nginx; -use crate::templates::php::fpm_pool_config as php_fpm_pool_config; -use crate::templates::systemd::{ - nodejs_service as systemd_nodejs_service, socket_service as systemd_socket_service, -}; -use crate::to_artifact::ToArtifact; -use std::fmt::Display; -use std::path::{Path, PathBuf}; - -pub trait SymbolBuilder { - type Prerequisites: ToArtifact; - fn prerequisites(resource: &R) -> Self::Prerequisites; - - type Symbol; - fn create( - resource: &R, - target: &R::Artifact, - inputs: ::Artifact, - ) -> Self::Symbol - where - R: Resource; -} - -pub struct DefaultBuilder; - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &Key) -> Self::Prerequisites {} - - type Symbol = KeySymbol; - fn create( - _resource: &Key, - target: & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - KeySymbol::new(StdCommandRunner, target.0.clone()) - } -} - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = Key; - fn prerequisites(resource: &Csr) -> Self::Prerequisites { - Key(resource.0.clone()) - } - - type Symbol = CsrSymbol; - fn create( - resource: &Csr, - target: & as Resource>::Artifact, - key: ::Artifact, - ) -> Self::Symbol { - CsrSymbol::new( - StdCommandRunner, - resource.0.clone(), - key.0, - target.0.clone(), - ) - } -} - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = ( - Csr, - AcmeRootCert, - AcmeAccountKey, - AcmeChallengesDir, - AcmeUser, - DefaultServer, - ); - fn prerequisites(resource: &Cert) -> Self::Prerequisites { - ( - Csr(resource.0.clone()), - AcmeRootCert, - AcmeAccountKey, - AcmeChallengesDir, - AcmeUser, - DefaultServer, - ) - } - - type Symbol = CertSymbol< - SetuidCommandRunner<'static, String, StdCommandRunner>, - SetuidCommandRunner<'static, String, StdCommandRunner>, - D, - PathBuf, - >; - fn create( - resource: &Cert, - target: & as Resource>::Artifact, - (csr, root_cert, account_key, challenges_dir, user_name, _): ::Artifact, - ) -> Self::Symbol { - CertSymbol::new( - resource.0.clone(), - SetuidCommandRunner::new(user_name.0, &StdCommandRunner), - root_cert.0, - account_key.0, - challenges_dir.0, - csr.0, - target.0.clone(), - ) - } -} - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = (Cert, AcmeRootCert); - fn prerequisites(resource: &CertChain) -> Self::Prerequisites { - (Cert(resource.0.clone()), AcmeRootCert) - } - - type Symbol = ConcatSymbol<[PathBuf; 2], PathBuf, PathBuf>; - fn create( - _resource: &CertChain, - target: & as Resource>::Artifact, - (cert, root_cert): ::Artifact, - ) -> Self::Symbol { - ConcatSymbol::new([cert.0, root_cert.0], target.0.clone()) - } -} - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = (CertChain, Key); - fn prerequisites(resource: &KeyAndCertBundle) -> Self::Prerequisites { - (CertChain(resource.0.clone()), Key(resource.0.clone())) - } - - type Symbol = ConcatSymbol<[PathBuf; 2], PathBuf, PathBuf>; - fn create( - _resource: &KeyAndCertBundle, - target: & as Resource>::Artifact, - (cert_chain, key): ::Artifact, - ) -> Self::Symbol { - ConcatSymbol::new([cert_chain.0, key.0], target.0.clone()) - } -} - -impl + Clone> SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &File

) -> Self::Prerequisites {} - - type Symbol = FileSymbol; - fn create( - resource: &File

, - _target: & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - FileSymbol::new(resource.0.clone(), resource.1.clone()) - } -} - -impl<'a, P: AsRef + Clone> SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &GitCheckout<'a, P>) -> Self::Prerequisites {} - - type Symbol = GitCheckoutSymbol; - fn create( - resource: &GitCheckout<'a, P>, - _target: & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - GitCheckoutSymbol::new(resource.0.clone(), resource.1, resource.2, StdCommandRunner) - } -} - -impl SymbolBuilder for DefaultBuilder { - type Prerequisites = AcmeChallengesNginxSnippet; - fn prerequisites(_resource: &DefaultServer) -> Self::Prerequisites { - AcmeChallengesNginxSnippet - } - - type Symbol = ( - FileSymbol, - ReloadServiceSymbol, - ); - fn create( - _resource: &DefaultServer, - target: &::Artifact, - challenges_snippet_path: ::Artifact, - ) -> Self::Symbol { - ( - FileSymbol::new( - target.0.clone(), - nginx::default_server(challenges_snippet_path), - ), - ReloadServiceSymbol::new(StdCommandRunner, "nginx"), - ) - } -} - -impl + Clone + Display> SymbolBuilder> for DefaultBuilder { - type Prerequisites = (CertChain, Key, AcmeChallengesNginxSnippet); - fn prerequisites(resource: &ServeCustom) -> Self::Prerequisites { - ( - CertChain(resource.0.clone()), - Key(resource.0.clone()), - AcmeChallengesNginxSnippet, - ) - } - - type Symbol = ( - FileSymbol, - ReloadServiceSymbol, - ); - fn create( - resource: &ServeCustom, - target: & as Resource>::Artifact, - (cert, key, challenges_snippet_path): ::Artifact, - ) -> Self::Symbol { - ( - FileSymbol::new( - target.0.clone(), - nginx::server_config( - &resource.0, - cert.0, - key.0, - &resource.1, - challenges_snippet_path, - ), - ), - ReloadServiceSymbol::new(StdCommandRunner, "nginx"), - ) - } -} - -impl> SymbolBuilder> for DefaultBuilder { - type Prerequisites = ( - PhpFpmPool, - CertChain, - Key, - AcmeChallengesNginxSnippet, - ); - fn prerequisites(resource: &ServePhp) -> Self::Prerequisites { - ( - PhpFpmPool(resource.0.clone(), 10), - CertChain(resource.0.clone()), - Key(resource.0.clone()), - AcmeChallengesNginxSnippet, - ) - } - - type Symbol = ( - FileSymbol, - ReloadServiceSymbol, - ); - fn create( - resource: &ServePhp, - target: & as Resource>::Artifact, - (pool, cert, key, challenges_snippet_path): ::Artifact, - ) -> Self::Symbol { - ( - FileSymbol::new( - target.0.clone(), - nginx::server_config( - &resource.0, - cert.0, - key.0, - nginx::php_snippet(resource.2, &pool.0, &resource.1) + &resource.3, - challenges_snippet_path, - ), - ), - ReloadServiceSymbol::new(StdCommandRunner, "nginx"), - ) - } -} - -impl> SymbolBuilder> - for DefaultBuilder -{ - type Prerequisites = ( - SystemdSocketService, - CertChain, - Key, - AcmeChallengesNginxSnippet, - ); - fn prerequisites(resource: &ServeService) -> Self::Prerequisites { - ( - SystemdSocketService( - resource.0.clone(), - resource.1, - resource.2.clone(), - resource.4.clone(), - resource.5, - ), - CertChain(resource.0.clone()), - Key(resource.0.clone()), - AcmeChallengesNginxSnippet, - ) - } - - type Symbol = ( - FileSymbol, - ReloadServiceSymbol, - ); - fn create( - resource: &ServeService, - target: & as Resource>::Artifact, - (socket, cert, key, challenges_snippet_path): ::Artifact, - ) -> Self::Symbol { - ( - FileSymbol::new( - target.0.clone(), - nginx::server_config( - &resource.0, - cert.0, - key.0, - nginx::proxy_snippet(&socket.0, &resource.3), - challenges_snippet_path, - ), - ), - ReloadServiceSymbol::new(StdCommandRunner, "nginx"), - ) - } -} - -impl + Clone + Display> SymbolBuilder> for DefaultBuilder { - type Prerequisites = (CertChain, Key, AcmeChallengesNginxSnippet); - fn prerequisites(resource: &ServeRedir) -> Self::Prerequisites { - ( - CertChain(resource.0.clone()), - Key(resource.0.clone()), - AcmeChallengesNginxSnippet, - ) - } - - type Symbol = ( - FileSymbol, - ReloadServiceSymbol, - ); - fn create( - resource: &ServeRedir, - target: & as Resource>::Artifact, - (cert, key, challenges_snippet_path): ::Artifact, - ) -> Self::Symbol { - ( - FileSymbol::new( - target.0.clone(), - nginx::server_config( - &resource.0, - cert.0, - key.0, - nginx::redir_snippet(resource.1.as_ref()), - challenges_snippet_path, - ), - ), - ReloadServiceSymbol::new(StdCommandRunner, "nginx"), - ) - } -} - -impl + Clone + Display, P: AsRef> SymbolBuilder> - for DefaultBuilder -{ - type Prerequisites = (CertChain, Key, AcmeChallengesNginxSnippet); - fn prerequisites(resource: &ServeStatic) -> Self::Prerequisites { - ( - CertChain(resource.0.clone()), - Key(resource.0.clone()), - AcmeChallengesNginxSnippet, - ) - } - - type Symbol = ( - FileSymbol, - ReloadServiceSymbol, - ); - fn create( - resource: &ServeStatic, - target: & as Resource>::Artifact, - (cert, key, challenges_snippet_path): ::Artifact, - ) -> Self::Symbol { - ( - FileSymbol::new( - target.0.clone(), - nginx::server_config( - &resource.0, - cert.0, - key.0, - nginx::static_snippet(resource.1.as_ref()), - challenges_snippet_path, - ), - ), - ReloadServiceSymbol::new(StdCommandRunner, "nginx"), - ) - } -} - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &PhpFpmPool) -> Self::Prerequisites {} - - type Symbol = ( - FileSymbol, - ReloadServiceSymbol, - ); - fn create( - resource: &PhpFpmPool, - (socket_path, conf_path, user_name, service_name): & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - ( - FileSymbol::new( - conf_path.0.clone(), - php_fpm_pool_config(&user_name.0, &socket_path.0, resource.1), - ), - ReloadServiceSymbol::new(StdCommandRunner, service_name.0.clone()), - ) - } -} - -impl> SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &SystemdSocketService) -> Self::Prerequisites {} - - type Symbol = ( - FileSymbol, - SystemdUserSessionSymbol<'static, String, StdCommandRunner>, - UserServiceSymbol<'static, PathBuf, String, StdCommandRunner>, - ); - fn create( - resource: &SystemdSocketService, - (socket_path, conf_path, user_name): & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - ( - FileSymbol::new( - conf_path.0.clone(), - if resource.4 { - systemd_nodejs_service(&resource.2, socket_path, &resource.3) - } else { - systemd_socket_service( - socket_path, - resource.2.as_ref().to_str().unwrap(), - &resource.3, - "", - ) - }, - ), - SystemdUserSessionSymbol::new(user_name.0.clone(), &StdCommandRunner), - UserServiceSymbol::new( - socket_path.0.clone(), - user_name.0.clone(), - resource.1, - &StdCommandRunner, - ), - ) - } -} - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &Dir

) -> Self::Prerequisites {} - - type Symbol = DirSymbol

; - fn create( - resource: &Dir

, - _target: & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - DirSymbol::new(resource.0.clone()) - } -} - -impl> SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &NpmInstall

) -> Self::Prerequisites {} - - type Symbol = NpmInstallSymbol<'static, P, StdCommandRunner>; - fn create( - resource: &NpmInstall

, - _target: & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - NpmInstallSymbol::new(resource.0.clone(), &StdCommandRunner) - } -} - -impl> SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &StoredDirectory

) -> Self::Prerequisites {} - - type Symbol = SavedDirectorySymbol; - fn create( - resource: &StoredDirectory

, - target: & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - SavedDirectorySymbol::new( - resource.1.clone(), - SimpleStorage::new(target.0.clone()), - StorageDirection::Store, - StdCommandRunner, - ) - } -} - -impl> SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &LoadedDirectory

) -> Self::Prerequisites {} - - type Symbol = SavedDirectorySymbol; - fn create( - resource: &LoadedDirectory

, - target: & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - SavedDirectorySymbol::new( - resource.1.clone(), - SimpleStorage::new(target.0.clone()), - StorageDirection::Load, - StdCommandRunner, - ) - } -} - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &UserForDomain) -> Self::Prerequisites {} - - type Symbol = UserSymbol; - fn create( - _resource: &UserForDomain, - (user_name, _home_path): & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - UserSymbol::new(user_name.0.clone(), StdCommandRunner) - } -} - -impl SymbolBuilder for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &User) -> Self::Prerequisites {} - - type Symbol = UserSymbol; - fn create( - resource: &User, - (): &::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - UserSymbol::new(resource.0.clone(), StdCommandRunner) - } -} - -impl + Clone> SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &Owner

) -> Self::Prerequisites {} - - type Symbol = OwnerSymbol; - fn create( - resource: &Owner

, - (): & as Resource>::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - OwnerSymbol::new(resource.1.clone(), resource.0.clone(), StdCommandRunner) - } -} - -impl SymbolBuilder for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &AcmeUser) -> Self::Prerequisites {} - - type Symbol = UserSymbol; - fn create( - _resource: &AcmeUser, - user_name: &::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - UserSymbol::new(user_name.0.clone(), StdCommandRunner) - } -} - -impl SymbolBuilder for DefaultBuilder { - type Prerequisites = AcmeUser; - fn prerequisites(_resource: &AcmeChallengesDir) -> Self::Prerequisites { - AcmeUser - } - - type Symbol = ( - DirSymbol, - OwnerSymbol, - ); - fn create( - _resource: &AcmeChallengesDir, - target: &::Artifact, - user_name: ::Artifact, - ) -> Self::Symbol { - ( - DirSymbol::new(target.0.clone()), - OwnerSymbol::new(target.0.clone(), user_name.0, StdCommandRunner), - ) - } -} - -impl SymbolBuilder for DefaultBuilder { - type Prerequisites = AcmeChallengesDir; - fn prerequisites(_resource: &AcmeChallengesNginxSnippet) -> Self::Prerequisites { - AcmeChallengesDir - } - - type Symbol = FileSymbol; - fn create( - _resource: &AcmeChallengesNginxSnippet, - target: &::Artifact, - challenges_dir: ::Artifact, - ) -> Self::Symbol { - FileSymbol::new( - target.0.clone(), - nginx::acme_challenges_snippet(challenges_dir), - ) - } -} - -impl SymbolBuilder for DefaultBuilder { - type Prerequisites = AcmeUser; - fn prerequisites(_resource: &AcmeAccountKey) -> Self::Prerequisites { - AcmeUser - } - - type Symbol = ( - KeySymbol, - OwnerSymbol, - ); - fn create( - _resource: &AcmeAccountKey, - target: &::Artifact, - user_name: ::Artifact, - ) -> Self::Symbol { - ( - KeySymbol::new(StdCommandRunner, target.0.clone()), - OwnerSymbol::new(target.0.clone(), user_name.0, StdCommandRunner), - ) - } -} - -impl SymbolBuilder for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &AcmeRootCert) -> Self::Prerequisites {} - - type Symbol = FileSymbol; - fn create( - _resource: &AcmeRootCert, - target: &::Artifact, - (): ::Artifact, - ) -> Self::Symbol { - FileSymbol::new(target.0.clone(), LETS_ENCRYPT_X3_CROSS_SIGNED) - } -} - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = (); - fn prerequisites(_resource: &MariaDbUser) -> Self::Prerequisites {} - - type Symbol = MariaDbUserSymbol<'static, String, StdCommandRunner>; - fn create( - _resource: &MariaDbUser, - user_name: & as Resource>::Artifact, - _: ::Artifact, - ) -> Self::Symbol { - MariaDbUserSymbol::new(user_name.0.clone(), &StdCommandRunner) - } -} - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = MariaDbUser; - fn prerequisites(resource: &MariaDbDatabase) -> Self::Prerequisites { - MariaDbUser(resource.0.clone()) - } - - type Symbol = ( - MariaDbDatabaseSymbol<'static, String, SimpleStorage, StdCommandRunner>, - MariaDbDumpSymbol<'static, String, StdCommandRunner, SimpleStorage>, - ); - fn create( - _resource: &MariaDbDatabase, - (db_name, _, data_path): & as Resource>::Artifact, - _: ::Artifact, - ) -> Self::Symbol { - let db_dump = SimpleStorage::new(data_path.0.clone()); - ( - MariaDbDatabaseSymbol::new(db_name.0.clone(), db_dump.clone(), &StdCommandRunner), - MariaDbDumpSymbol::new(db_name.0.clone(), db_dump, &StdCommandRunner), - ) - } -} - -impl> SymbolBuilder> for DefaultBuilder { - type Prerequisites = Dir; - fn prerequisites(resource: &WordpressPlugin

) -> Self::Prerequisites { - Dir(resource.0.as_ref().join("wp-content/plugins")) - } - - type Symbol = WordpressPluginSymbol<'static, P, &'static str, StdCommandRunner>; - fn create( - resource: &WordpressPlugin

, - (): & as Resource>::Artifact, - _: ::Artifact, - ) -> Self::Symbol { - WordpressPluginSymbol::new(resource.0.clone(), resource.1, &StdCommandRunner) - } -} - -impl> SymbolBuilder> for DefaultBuilder { - type Prerequisites = Dir; - fn prerequisites(resource: &WordpressTranslation

) -> Self::Prerequisites { - Dir(resource.0.as_ref().join("wp-content/languages")) - } - - type Symbol = WordpressTranslationSymbol<'static, &'static str, PathBuf, StdCommandRunner>; - fn create( - resource: &WordpressTranslation

, - (): & as Resource>::Artifact, - _: ::Artifact, - ) -> Self::Symbol { - WordpressTranslationSymbol::new( - resource.0.as_ref().join("wp-content/languages"), - resource.1, - resource.2, - &StdCommandRunner, - ) - } -} - -impl SymbolBuilder> for DefaultBuilder { - type Prerequisites = UserForDomain; - fn prerequisites(resource: &Cron) -> Self::Prerequisites { - UserForDomain(resource.0.clone()) - } - - type Symbol = CronSymbol<'static, String, String, StdCommandRunner>; - fn create( - resource: &Cron, - (): & as Resource>::Artifact, - user_name: ::Artifact, - ) -> Self::Symbol { - CronSymbol::new((user_name.0).0, resource.1.clone(), &StdCommandRunner) - } -} diff --git a/src/factory.rs b/src/factory.rs new file mode 100644 index 0000000..e529c44 --- /dev/null +++ b/src/factory.rs @@ -0,0 +1,125 @@ +use regex::Regex; + +use crate::command_runner::CommandRunner; +use crate::loggers::StdErrLogger; +use crate::repository::SymbolRepository; +use crate::resources::Resource; +use crate::schema::{ + NonRepeatingSymbolRunner, ReportingSymbolRunner, RequirementsResolvingSymbolRunner, +}; +use crate::symbols::dir::Dir; +use crate::symbols::list::List; +use crate::symbols::owner::Owner; +use crate::symbols::systemd::user_session::SystemdUserSession; +use crate::symbols::tls::{TlsCsr, TlsKey}; +use crate::symbols::user::SystemUserAdder; +use crate::symbols::user::{User, UserAdder}; +use crate::symbols::{Symbol, SymbolRunner}; + +#[derive(Default)] +pub struct Factory {} + +impl Factory { + pub fn new() -> Self { + Self::default() + } + + pub fn get_repo<'a, CR: CommandRunner>( + &self, + command_runner: &'a CR, + ) -> DefaultSymbolRepository<'a, SystemUserAdder<'a, CR>, CR> { + DefaultSymbolRepository::new(command_runner) + } + + pub fn get_symbol_runner<'a, RUNNER: SymbolRunner, REPO: SymbolRepository<'a>>( + &self, + symbol_runner: &'a RUNNER, + repo: &'a REPO, + ) -> Box { + let runner1 = ReportingSymbolRunner::new(symbol_runner, StdErrLogger); + let runner2 = NonRepeatingSymbolRunner::new(runner1); + Box::new(RequirementsResolvingSymbolRunner::new(runner2, repo)) + } +} + +pub struct DefaultSymbolRepository<'a, A: 'a + UserAdder, C: CommandRunner> { + user_adder: A, + command_runner: &'a C, + + home: Regex, + home_config: Regex, + csr: Regex, + private_key: Regex, + systemd_linger: Regex, +} + +impl<'a, C: 'a + CommandRunner> DefaultSymbolRepository<'a, SystemUserAdder<'a, C>, C> { + pub fn new(command_runner: &'a C) -> Self { + Self { + command_runner, + user_adder: SystemUserAdder::new(command_runner), + home: Regex::new("^/home/([^/]+)$").unwrap(), + home_config: Regex::new("^/home/([^/]+)/.config(?:/|$)").unwrap(), + csr: Regex::new("^/etc/ssl/local_certs/([^/]+).csr$").unwrap(), + private_key: Regex::new("^/etc/ssl/private/([^/]+).key$").unwrap(), + systemd_linger: Regex::new("^/var/lib/systemd/linger/([^/]+)$").unwrap(), + } + } +} + +impl<'a, C: CommandRunner> SymbolRepository<'a> + for DefaultSymbolRepository<'a, SystemUserAdder<'a, C>, C> +{ + fn get_symbol(&'a self, resource: &Resource) -> Option> { + match resource.get_type() { + "user" => Some(Box::new(User::new( + resource.get_value().to_string().into(), + self.command_runner, + &self.user_adder, + ))), + "dir" => { + let value = resource.get_value(); + Some(if let Some(matches) = self.home_config.captures(value) { + Box::new(List::new(vec![ + Box::new(Dir::new(value.to_string())), + Box::new(Owner::new( + value.to_string(), + matches[1].to_string(), + self.command_runner, + )), + ])) + } else if let Some(matches) = self.home.captures(value) { + Box::new(User::new( + matches[1].to_string().into(), + self.command_runner, + &self.user_adder, + )) + } else { + Box::new(Dir::new(value.to_string())) + }) + } + "file" => { + let value = resource.get_value(); + if let Some(matches) = self.csr.captures(value) { + Some(Box::new(TlsCsr::new( + matches[1].to_string().into(), + self.command_runner, + ))) + } else if let Some(matches) = self.private_key.captures(value) { + Some(Box::new(TlsKey::new( + matches[1].to_string().into(), + self.command_runner, + ))) + } else if let Some(matches) = self.systemd_linger.captures(value) { + Some(Box::new(SystemdUserSession::new( + matches[1].to_string(), + self.command_runner, + ))) + } else { + None + } + } + _ => None, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 7a09805..57ed75b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![warn( +#![deny( macro_use_extern_crate, meta_variable_misuse, non_ascii_idents, @@ -32,20 +32,10 @@ pub mod bin; pub mod build; #[macro_use] pub mod command_runner; +pub mod factory; pub mod loggers; +pub mod repository; pub mod resources; pub mod schema; pub mod storage; pub mod symbols; -pub mod templates; - -mod artifacts; -mod builder; -mod locator; -mod setup; -pub mod static_files; -mod to_artifact; - -pub use builder::{DefaultBuilder, SymbolBuilder}; -pub use locator::{DefaultLocator, DefaultPolicy, Policy, ResourceLocator}; -pub use setup::Setup; diff --git a/src/locator.rs b/src/locator.rs deleted file mode 100644 index 4340cdb..0000000 --- a/src/locator.rs +++ /dev/null @@ -1,443 +0,0 @@ -use crate::artifacts::{ - DatabaseName as DatabaseNameArtifact, Path as PathArtifact, ServiceName as ServiceNameArtifact, - UserName as UserNameArtifact, -}; -use crate::resources::{ - AcmeAccountKey, AcmeChallengesDir, AcmeChallengesNginxSnippet, AcmeRootCert, AcmeUser, Cert, - CertChain, Cron, Csr, DefaultServer, Dir, File, GitCheckout, Key, KeyAndCertBundle, - LoadedDirectory, MariaDbDatabase, MariaDbUser, NpmInstall, Owner, PhpFpmPool, Resource, - ServeCustom, ServePhp, ServeRedir, ServeService, ServeStatic, StoredDirectory, - SystemdSocketService, User, UserForDomain, WordpressPlugin, WordpressTranslation, -}; -use crate::to_artifact::ToArtifact; -use std::fmt::Display; -use std::marker::PhantomData; -use std::path::{Path, PathBuf}; - -pub trait Policy { - fn acme_user() -> &'static str { - "acme" - } - fn user_home(user_name: &str) -> PathBuf { - format!("/home/{}", user_name).into() - } - - fn user_name_for_domain(domain_name: &'_ str) -> String { - domain_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part) - } - - fn php_version() -> &'static str { - "7.0" - } - - fn path_for_data(name: impl Display) -> PathBuf { - ("/root/data".as_ref() as &Path).join(format!("_{}", name)) - } -} - -pub struct DefaultPolicy; - -impl Policy for DefaultPolicy {} - -pub trait ResourceLocator { - type Prerequisites: ToArtifact; - fn locate(resource: &R) -> (R::Artifact, Self::Prerequisites) - where - R: Resource; -} - -pub struct DefaultLocator

{ - phantom: PhantomData

, -} - -impl> ResourceLocator> for DefaultLocator

{ - type Prerequisites = Dir; - fn locate(resource: &Key) -> ( as Resource>::Artifact, Self::Prerequisites) { - ( - PathArtifact(format!("/etc/ssl/private/{}.key", resource.0.as_ref()).into()), - Dir("/etc/ssl/private".into()), - ) - } -} - -impl> ResourceLocator> for DefaultLocator

{ - type Prerequisites = Dir; - fn locate(resource: &Csr) -> ( as Resource>::Artifact, Self::Prerequisites) { - ( - PathArtifact(format!("/etc/ssl/local_certs/{}.csr", resource.0.as_ref()).into()), - Dir("/etc/ssl/local_certs".into()), - ) - } -} - -impl> ResourceLocator> for DefaultLocator

{ - type Prerequisites = Dir; - fn locate(resource: &Cert) -> ( as Resource>::Artifact, Self::Prerequisites) { - ( - PathArtifact(format!("/etc/ssl/local_certs/{}.crt", resource.0.as_ref()).into()), - Dir("/etc/ssl/local_certs".into()), - ) - } -} - -impl> ResourceLocator> for DefaultLocator

{ - type Prerequisites = Dir; - fn locate( - resource: &CertChain, - ) -> ( as Resource>::Artifact, Self::Prerequisites) { - ( - PathArtifact(format!("/etc/ssl/local_certs/{}.chained.crt", resource.0.as_ref()).into()), - Dir("/etc/ssl/local_certs".into()), - ) - } -} - -impl> ResourceLocator> for DefaultLocator

{ - type Prerequisites = Dir; - fn locate( - resource: &KeyAndCertBundle, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - ( - PathArtifact(format!("/etc/ssl/private/{}.with_key.crt", resource.0.as_ref()).into()), - Dir("/etc/ssl/private".into()), - ) - } -} - -impl<'a, POLICY, P: AsRef> ResourceLocator> for DefaultLocator { - type Prerequisites = Dir; - fn locate(resource: &File

) -> ( as Resource>::Artifact, Self::Prerequisites) { - ((), Dir(resource.0.as_ref().parent().unwrap().into())) - } -} - -impl<'a, POLICY, P: AsRef> ResourceLocator> for DefaultLocator { - type Prerequisites = Dir; - fn locate( - resource: &GitCheckout<'a, P>, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - ((), Dir(resource.0.as_ref().parent().unwrap().into())) - } -} - -impl> ResourceLocator> for DefaultLocator { - type Prerequisites = Option>; - fn locate(resource: &Dir

) -> ( as Resource>::Artifact, Self::Prerequisites) { - ((), resource.0.as_ref().parent().map(|p| Dir(p.into()))) - } -} - -impl ResourceLocator> for DefaultLocator { - type Prerequisites = (); - fn locate( - _resource: &NpmInstall

, - ) -> ( as Resource>::Artifact, Self::Prerequisites) { - ((), ()) - } -} - -impl> ResourceLocator> - for DefaultLocator -{ - type Prerequisites = (); - fn locate( - resource: &StoredDirectory

, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - (PathArtifact(POLICY::path_for_data(resource.0)), ()) - } -} - -impl> ResourceLocator> - for DefaultLocator -{ - type Prerequisites = Dir; - fn locate( - resource: &LoadedDirectory

, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - ( - PathArtifact(POLICY::path_for_data(resource.0)), - Dir(resource.1.as_ref().parent().unwrap().into()), - ) - } -} - -impl ResourceLocator for DefaultLocator

{ - type Prerequisites = Dir; - fn locate( - _resource: &AcmeAccountKey, - ) -> (::Artifact, Self::Prerequisites) { - let acme_user = P::acme_user(); - let home = P::user_home(acme_user); - (PathArtifact(home.join("account.key")), Dir(home)) - } -} - -impl ResourceLocator for DefaultLocator

{ - type Prerequisites = (); - fn locate(_resource: &AcmeUser) -> (::Artifact, Self::Prerequisites) { - let acme_user = P::acme_user(); - (UserNameArtifact(acme_user.into()), ()) - } -} - -impl ResourceLocator for DefaultLocator

{ - type Prerequisites = Dir; - fn locate( - _resource: &AcmeChallengesDir, - ) -> ( - ::Artifact, - Self::Prerequisites, - ) { - let acme_user = P::acme_user(); - let home = P::user_home(acme_user); - (PathArtifact(home.join("challenges")), Dir(home)) - } -} - -impl ResourceLocator for DefaultLocator

{ - type Prerequisites = (); - fn locate( - _resource: &AcmeChallengesNginxSnippet, - ) -> ( - ::Artifact, - Self::Prerequisites, - ) { - ( - PathArtifact("/etc/nginx/snippets/acme-challenge.conf".into()), - (), - ) - } -} - -impl ResourceLocator for DefaultLocator

{ - type Prerequisites = Dir; - fn locate( - _resource: &AcmeRootCert, - ) -> (::Artifact, Self::Prerequisites) { - let acme_user = P::acme_user(); - let home = P::user_home(acme_user); - ( - PathArtifact(home.join("lets_encrypt_x3_cross_signed.pem")), - Dir(home), - ) - } -} - -impl> ResourceLocator> for DefaultLocator

{ - type Prerequisites = (); - fn locate( - resource: &UserForDomain, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - let user_name = P::user_name_for_domain(resource.0.as_ref()); - let home = P::user_home(&user_name); - ((UserNameArtifact(user_name), PathArtifact(home)), ()) - } -} - -impl

ResourceLocator for DefaultLocator

{ - type Prerequisites = (); - fn locate(_resource: &User) -> (::Artifact, Self::Prerequisites) { - ((), ()) - } -} - -impl ResourceLocator> for DefaultLocator { - type Prerequisites = User; - fn locate(resource: &Owner

) -> ( as Resource>::Artifact, Self::Prerequisites) { - ((), User(resource.0.clone())) - } -} - -impl

ResourceLocator for DefaultLocator

{ - type Prerequisites = (); - fn locate( - _resource: &DefaultServer, - ) -> (::Artifact, Self::Prerequisites) { - (PathArtifact("/etc/nginx/sites-enabled/default".into()), ()) - } -} - -impl, POLICY> ResourceLocator> for DefaultLocator { - type Prerequisites = (); - fn locate( - resource: &ServeCustom, - ) -> ( as Resource>::Artifact, Self::Prerequisites) { - ( - PathArtifact(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)), - (), - ) - } -} - -impl, P, POLICY> ResourceLocator> for DefaultLocator { - type Prerequisites = (); - fn locate( - resource: &ServePhp, - ) -> ( as Resource>::Artifact, Self::Prerequisites) { - ( - PathArtifact(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)), - (), - ) - } -} - -impl, P, POLICY> ResourceLocator> for DefaultLocator { - type Prerequisites = (); - fn locate( - resource: &ServeService, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - ( - PathArtifact(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)), - (), - ) - } -} - -impl, POLICY> ResourceLocator> for DefaultLocator { - type Prerequisites = (); - fn locate( - resource: &ServeRedir, - ) -> ( as Resource>::Artifact, Self::Prerequisites) { - ( - PathArtifact(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)), - (), - ) - } -} - -impl, P, POLICY> ResourceLocator> for DefaultLocator { - type Prerequisites = (); - fn locate( - resource: &ServeStatic, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - ( - PathArtifact(("/etc/nginx/sites-enabled/".as_ref() as &Path).join(&resource.0)), - (), - ) - } -} - -impl, P: Policy> ResourceLocator> for DefaultLocator

{ - type Prerequisites = (); - fn locate( - resource: &PhpFpmPool, - ) -> ( as Resource>::Artifact, Self::Prerequisites) { - let ((user, _), ()) = Self::locate(&UserForDomain(&resource.0)); - let php_version = P::php_version(); - ( - ( - PathArtifact(format!("/run/php/{}.sock", user.0).into()), - PathArtifact(format!("/etc/php/{}/fpm/pool.d/{}.conf", php_version, user.0).into()), - user, - ServiceNameArtifact(format!("php{}-fpm", php_version)), - ), - (), - ) - } -} - -impl, P, POLICY: Policy> ResourceLocator> - for DefaultLocator -{ - type Prerequisites = (Dir, Owner); - fn locate( - resource: &SystemdSocketService, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - let ((user_name, home_path), ()) = Self::locate(&UserForDomain(&resource.0)); - let config = home_path.0.join(".config"); - let service_dir_path = config.join("systemd/user"); - ( - ( - PathArtifact(format!("/var/tmp/{}-{}.socket", user_name.0, resource.1).into()), - PathArtifact(service_dir_path.join(format!("{}.service", resource.1))), - user_name.clone(), - ), - (Dir(service_dir_path), Owner(user_name.0, config)), - ) - } -} - -impl, P: Policy> ResourceLocator> for DefaultLocator

{ - type Prerequisites = (); - fn locate( - resource: &MariaDbDatabase, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0)); - ( - ( - DatabaseNameArtifact(user_name.0.clone()), - user_name.clone(), - PathArtifact(P::path_for_data(format!("{}.sql", user_name.0))), - ), - (), - ) - } -} - -impl, P: Policy> ResourceLocator> for DefaultLocator

{ - type Prerequisites = (); - fn locate( - resource: &MariaDbUser, - ) -> ( as Resource>::Artifact, Self::Prerequisites) { - let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0)); - ((user_name), ()) - } -} - -impl ResourceLocator> for DefaultLocator { - type Prerequisites = (); - fn locate( - _resource: &WordpressPlugin

, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - ((), ()) - } -} - -impl ResourceLocator> for DefaultLocator { - type Prerequisites = (); - fn locate( - _resource: &WordpressTranslation

, - ) -> ( - as Resource>::Artifact, - Self::Prerequisites, - ) { - ((), ()) - } -} - -impl ResourceLocator> for DefaultLocator

{ - type Prerequisites = (); - fn locate(_resource: &Cron) -> ( as Resource>::Artifact, Self::Prerequisites) { - ((), ()) - } -} diff --git a/src/repository.rs b/src/repository.rs new file mode 100644 index 0000000..5634b41 --- /dev/null +++ b/src/repository.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; + +use crate::resources::Resource; +use crate::symbols::Symbol; + +pub trait SymbolRepository<'a> { + fn get_symbol(&'a self, resource: &Resource) -> Option>; +} + +impl<'a, C> SymbolRepository<'a> for C +where + C: Fn(&Resource) -> Option>, +{ + fn get_symbol(&'a self, resource: &Resource) -> Option> { + self(resource) + } +} + +pub struct DispatchingSymbolRepository<'a> { + repositories: HashMap<&'a str, Box + 'a>>, +} + +impl<'a> DispatchingSymbolRepository<'a> { + pub fn new(repositories: HashMap<&'a str, Box + 'a>>) -> Self { + DispatchingSymbolRepository { repositories } + } +} + +impl<'a> SymbolRepository<'a> for DispatchingSymbolRepository<'a> { + fn get_symbol(&'a self, resource: &Resource) -> Option> { + self + .repositories + .get(resource.get_type()) + .and_then(|repo| repo.get_symbol(resource)) + } +} diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 4e717af..f2f1363 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -1,282 +1,15 @@ -use crate::artifacts::{ - DatabaseName as DatabaseNameArtifact, Path as PathArtifact, ServiceName as ServiceNameArtifact, - UserName as UserNameArtifact, -}; -use std::hash::Hash; -use std::path::PathBuf; +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct Resource(pub String, pub String); -pub trait Resource { - type Artifact; -} +impl<'a> Resource { + pub fn new, B: Into>(rtype: A, value: B) -> Self { + Self(rtype.into(), value.into()) + } -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct Key(pub D); -impl Resource for Key { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct Csr(pub D); -impl Resource for Csr { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct Cert(pub D); -impl Resource for Cert { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct CertChain(pub D); -impl Resource for CertChain { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct KeyAndCertBundle(pub D); -impl Resource for KeyAndCertBundle { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct File

(pub P, pub String); -impl<'a, P> Resource for File

{ - type Artifact = (); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct GitCheckout<'a, P>(pub P, pub &'a str, pub &'a str); -impl<'a, P> Resource for GitCheckout<'a, P> { - type Artifact = (); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct Dir

(pub P); -impl

Resource for Dir

{ - type Artifact = (); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct UserForDomain(pub D); -impl Resource for UserForDomain { - type Artifact = (UserNameArtifact, PathArtifact); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct AcmeAccountKey; -impl Resource for AcmeAccountKey { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct AcmeUser; -impl Resource for AcmeUser { - type Artifact = UserNameArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct AcmeRootCert; -impl Resource for AcmeRootCert { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct AcmeChallengesDir; -impl Resource for AcmeChallengesDir { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct AcmeChallengesNginxSnippet; -impl Resource for AcmeChallengesNginxSnippet { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct StoredDirectory

(pub &'static str, pub P); -impl

Resource for StoredDirectory

{ - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct LoadedDirectory

(pub &'static str, pub P); -impl

Resource for LoadedDirectory

{ - type Artifact = PathArtifact; -} - -pub fn get_saved_directory( - storage_name: &'static str, - target: P, -) -> (StoredDirectory

, LoadedDirectory

) { - ( - StoredDirectory(storage_name, target.clone()), - LoadedDirectory(storage_name, target), - ) -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct User(pub String); -impl Resource for User { - type Artifact = (); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct SystemdSocketService(pub D, pub &'static str, pub P, pub P, pub bool); -impl Resource for SystemdSocketService { - type Artifact = (PathArtifact, PathArtifact, UserNameArtifact); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct NpmInstall

(pub P); -impl

Resource for NpmInstall

{ - type Artifact = (); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct Owner

(pub String, pub P); -impl

Resource for Owner

{ - type Artifact = (); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct ServeCustom(pub D, pub String); -impl Resource for ServeCustom { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct ServePhp(pub D, pub P, pub &'static str, pub String, pub usize); -impl Resource for ServePhp { - type Artifact = PathArtifact; -} - -// Domain, service name, exec, static, dir -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct ServeService(pub D, pub &'static str, pub P, pub P, pub P, pub bool); -impl Resource for ServeService { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct ServeRedir(pub D, pub D); -impl Resource for ServeRedir { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct ServeStatic(pub D, pub P); -impl Resource for ServeStatic { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct DefaultServer; -impl Resource for DefaultServer { - type Artifact = PathArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct PhpFpmPool(pub D, pub usize); -impl Resource for PhpFpmPool { - type Artifact = ( - PathArtifact, - PathArtifact, - UserNameArtifact, - ServiceNameArtifact, - ); -} - -// A single domain could possibly need multiple databases -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct MariaDbDatabase(pub D); -impl Resource for MariaDbDatabase { - type Artifact = (DatabaseNameArtifact, UserNameArtifact, PathArtifact); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct MariaDbUser(pub D); -impl Resource for MariaDbUser { - type Artifact = UserNameArtifact; -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct WordpressPlugin

(pub P, pub &'static str); -impl

Resource for WordpressPlugin

{ - type Artifact = (); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct WordpressTranslation

(pub P, pub &'static str, pub &'static str); -impl

Resource for WordpressTranslation

{ - type Artifact = (); -} - -#[derive(Debug, Hash, PartialEq, Eq)] -pub struct Cron(pub D, pub String); -impl Resource for Cron { - type Artifact = (); -} - -pub trait BorrowResource { - fn borrow_resource(&self) -> Option<&R>; -} - -macro_rules! default_resources { - ( $($name:ident: $type:ty,)* ) => { - #[derive(Debug, PartialEq, Eq, Hash)] - pub enum DefaultResources<'a, D> { - $( $name($type) ),* - } - - $(impl<'a, D> From<$type> for DefaultResources<'a, D> { - fn from(from: $type) -> Self { - Self::$name(from) - } - })* - - $(impl<'a, D> BorrowResource<$type> for DefaultResources<'a, D> { - fn borrow_resource(&self) -> Option<&$type> { - match self { - Self::$name(v) => Some(v), - _ => None - } - } - })* + pub fn get_type(&self) -> &str { + &self.0 + } + pub fn get_value(&self) -> &str { + &self.1 } } - -default_resources!( - AcmeAccountKey: AcmeAccountKey, - AcmeChallengesDir: AcmeChallengesDir, - AcmeChallengesNginxSnippet: AcmeChallengesNginxSnippet, - AcmeRootCert: AcmeRootCert, - AcmeUser: AcmeUser, - Cert: Cert, - CertChain: CertChain, - Cron: Cron, - Csr: Csr, - DefaultServer: DefaultServer, - Dir: Dir, - File: File, - GitCheckout: GitCheckout<'a, PathBuf>, - Key: Key, - KeyAndCertBundle: KeyAndCertBundle, - LoadedDirectory: LoadedDirectory, - MariaDbDatabase: MariaDbDatabase, - MariaDbUser: MariaDbUser, - SystemdSocketService: SystemdSocketService, - NpmInstall: NpmInstall, - Owner: Owner, - PhpFpmPool: PhpFpmPool, - ServeCustom: ServeCustom, - ServeService: ServeService, - ServePhp: ServePhp, - ServeRedir: ServeRedir, - ServeStatic: ServeStatic, - StoredDirectory: StoredDirectory, - User: User, - UserForDomain: UserForDomain, - WordpressPlugin: WordpressPlugin, - WordpressTranslation: WordpressTranslation, -); diff --git a/src/schema.rs b/src/schema.rs index 8b1cc94..9fe811c 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,19 +1,10 @@ -use crate::loggers::Logger; -use crate::symbols::Symbol; use std::cell::RefCell; use std::error::Error; use std::fmt; -use std::fmt::Debug; -pub trait SymbolRunner { - fn run_symbol(&self, symbol: &S, force: bool) -> Result>; -} - -impl SymbolRunner for Box { - fn run_symbol(&self, symbol: &S, force: bool) -> Result> { - (**self).run_symbol(symbol, force) - } -} +use crate::loggers::Logger; +use crate::repository::SymbolRepository; +use crate::symbols::{Symbol, SymbolRunner}; #[derive(Debug)] pub enum SymbolRunError { @@ -55,47 +46,31 @@ impl InitializingSymbolRunner { logger: RefCell::new(logger), } } - - fn exec_symbol(&self, symbol: &S) -> Result<(), Box> { - let mut logger = self.logger.borrow_mut(); - logger.write(format!("Executing {:?}", symbol).as_str()); - symbol.execute()?; - let target_reached = symbol.target_reached()?; - logger.debug( - format!( - "Symbol reports target_reached: {:?} (should be true)", - target_reached - ) - .as_str(), - ); - if target_reached { - Ok(()) - } else { - Err(Box::new(SymbolRunError::ExecuteDidNotReach(()))) - } - } } impl SymbolRunner for InitializingSymbolRunner { - fn run_symbol(&self, symbol: &S, force: bool) -> Result> { + fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { let mut logger = self.logger.borrow_mut(); - let executed = if force { - logger.debug("Forcing symbol execution"); - drop(logger); - self.exec_symbol(symbol)?; - true + let target_reached = symbol.target_reached()?; + if target_reached { + logger.write(format!("{} already reached", symbol).as_str()); } else { + logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str()); + logger.write(format!("Executing {}", symbol).as_str()); + symbol.execute()?; let target_reached = symbol.target_reached()?; - if target_reached { - logger.write(format!("{:?} already reached", symbol).as_str()); - } else { - logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str()); - drop(logger); - self.exec_symbol(symbol)?; + logger.debug( + format!( + "Symbol reports target_reached: {:?} (should be true)", + target_reached + ) + .as_str(), + ); + if !target_reached { + return Err(Box::new(SymbolRunError::ExecuteDidNotReach(()))); } - !target_reached - }; - Ok(executed) + } + Ok(()) } } @@ -112,26 +87,24 @@ impl DrySymbolRunner { } impl SymbolRunner for DrySymbolRunner { - fn run_symbol(&self, symbol: &S, force: bool) -> Result> { + fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { let mut logger = self.logger.borrow_mut(); - let would_execute = if force { - logger.write(format!("Would force-execute {:?}", symbol).as_str()); - true - } else { - let target_reached = symbol.target_reached()?; - logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str()); - if !target_reached { - logger.write(format!("Would execute {:?}", symbol).as_str()); - } - !target_reached - }; - Ok(would_execute) + let target_reached = symbol.target_reached()?; + logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str()); + if !target_reached { + logger.write(format!("Would execute {}", symbol).as_str()); + } + Ok(()) } } -pub struct ReportingSymbolRunner<'a, R, L>(&'a R, RefCell); +pub struct ReportingSymbolRunner<'a, R: SymbolRunner, L: Logger>(&'a R, RefCell); -impl<'a, R, L> ReportingSymbolRunner<'a, R, L> { +impl<'a, R, L> ReportingSymbolRunner<'a, R, L> +where + R: SymbolRunner, + L: Logger, +{ pub fn new(symbol_runner: &'a R, logger: L) -> Self { ReportingSymbolRunner(symbol_runner, RefCell::new(logger)) } @@ -142,20 +115,92 @@ where R: SymbolRunner, L: Logger, { - fn run_symbol(&self, symbol: &S, force: bool) -> Result> { + fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { let mut logger = self.1.borrow_mut(); - logger.debug(format!("Running symbol {:?}", symbol).as_str()); - let res = self.0.run_symbol(symbol, force); + logger.debug(format!("Running symbol {}", symbol).as_str()); + let res = self.0.run_symbol(symbol); if let Err(ref e) = res { - logger.write(format!("Failed on {:?} with {}, aborting.", symbol, e).as_str()) + logger.write(format!("Failed on {} with {}, aborting.", symbol, e).as_str()) } else { - logger.debug(format!("Successfully finished {:?}", symbol).as_str()) + logger.debug(format!("Successfully finished {}", symbol).as_str()) } res } } -/* +use crate::resources::Resource; +use std::collections::HashSet; + +pub struct NonRepeatingSymbolRunner { + upstream: R, + done: RefCell>, +} + +impl NonRepeatingSymbolRunner +where + R: SymbolRunner, +{ + pub fn new(symbol_runner: R) -> Self { + Self { + upstream: symbol_runner, + done: RefCell::new(HashSet::new()), + } + } +} + +impl SymbolRunner for NonRepeatingSymbolRunner { + fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { + if let Some(resources) = symbol.provides() { + let mut done = self.done.borrow_mut(); + let mut has_to_run = false; + for resource in resources { + if !done.contains(&resource) { + has_to_run = true; + assert!(done.insert(resource.clone())); + } + } + if !has_to_run { + return Ok(()); + } + } + self.upstream.run_symbol(&*symbol) + } +} +use std::marker::PhantomData; + +pub struct RequirementsResolvingSymbolRunner<'a, 's, R: 'a + SymbolRunner, G: SymbolRepository<'s>>( + R, + &'a G, + PhantomData>, +); + +impl<'s, 'a: 's, R, G> RequirementsResolvingSymbolRunner<'a, 's, R, G> +where + R: SymbolRunner, + G: SymbolRepository<'s>, +{ + pub fn new(symbol_runner: R, symbol_repo: &'a G) -> Self { + RequirementsResolvingSymbolRunner(symbol_runner, symbol_repo, PhantomData) + } +} + +impl<'s, 'a: 's, R, G> SymbolRunner for RequirementsResolvingSymbolRunner<'a, 's, R, G> +where + R: SymbolRunner, + G: SymbolRepository<'s>, +{ + fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { + for resource in symbol.get_prerequisites() { + if let Some(dep) = self.1.get_symbol(&resource) { + dep.as_action(self).run()?; + } + } + self.0.run_symbol(&*symbol) + } +} + +// FIXME: Add ExpectingSymbolRunner + #[cfg(test)] mod test { use std::cell::RefCell; @@ -165,7 +210,7 @@ mod test { use crate::loggers::Logger; use crate::schema::InitializingSymbolRunner; use crate::schema::SymbolRunner; - use crate::symbols::Symbol; + use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction}; #[derive(Debug, PartialEq, Clone)] enum DummySymbolError { @@ -184,7 +229,6 @@ mod test { } } - #[derive(Debug)] struct DummySymbol<'a> { _execute: &'a dyn Fn() -> Result<(), Box>, _target_reached: &'a dyn Fn() -> Result>, @@ -197,6 +241,23 @@ mod test { fn execute(&self) -> Result<(), Box> { (self._execute)() } + + fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box + where + Self: 'a, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } + } + + impl<'a> fmt::Display for DummySymbol<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Dummy symbol") + } } impl<'a> DummySymbol<'a> { @@ -275,4 +336,3 @@ mod test { assert_eq!(err.description(), "Description"); } } -*/ diff --git a/src/setup.rs b/src/setup.rs deleted file mode 100644 index ebf4e71..0000000 --- a/src/setup.rs +++ /dev/null @@ -1,345 +0,0 @@ -use crate::resources::{BorrowResource, DefaultResources, Resource}; -use crate::schema::SymbolRunner; -use crate::symbols::Symbol; -use crate::to_artifact::ToArtifact; -use crate::{DefaultBuilder, DefaultLocator, ResourceLocator, SymbolBuilder}; -use std::collections::HashSet; -use std::error::Error; -use std::fmt::Debug; -use std::hash::Hash; -use std::marker::PhantomData; - -pub trait CanHandle { - fn handle(&mut self, x: X) -> Result<(X::Artifact, bool), Box>; -} - -macro_rules! can_handle { - ( $($name:ident)* ) => ( - #[allow(non_snake_case)] - impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, $($name: Resource,)*> - CanHandle<($($name,)*)> - for Setup<_SR, _L, _R, _B> - where - $( - _B: SymbolBuilder<$name>, - <_B as SymbolBuilder<$name>>::Symbol: Runnable + Debug, - _L: ResourceLocator<$name>, - _R: From<$name> + BorrowResource<$name>, - Self: CanHandle<<_B as SymbolBuilder<$name>>::Prerequisites> + - CanHandle<<_L as ResourceLocator<$name>>::Prerequisites> - ),* - { - fn handle(&mut self, ($($name,)*): ($($name,)*)) -> Result<(($($name::Artifact,)*), bool), Box> - { - $(let $name = self.add($name)?;)* - Ok((($($name.0,)*), false $(|| $name.1)*)) - } - } - ); -} - -for_each_tuple!(can_handle); - -// This is for self-referential T -// FIXME: Wait for specialization -impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, T: Resource> CanHandle> - for Setup<_SR, _L, _R, _B> -where - _B: SymbolBuilder, - <_B as SymbolBuilder>::Symbol: Runnable + Debug, - _L: ResourceLocator>, - _R: From + BorrowResource, - Self: CanHandle<<_B as SymbolBuilder>::Prerequisites>, -{ - fn handle( - &mut self, - r: Option, - ) -> Result<( as ToArtifact>::Artifact, bool), Box> { - Ok(match r { - Some(r) => { - let (result, did_run) = self.add(r)?; - (Some(result), did_run) - } - None => (None, false), - }) - } -} - -impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, T: Resource> CanHandle for Setup<_SR, _L, _R, _B> -where - _B: SymbolBuilder, - <_B as SymbolBuilder>::Symbol: Runnable + Debug, - _L: ResourceLocator, - _R: From + Debug + BorrowResource, - Self: CanHandle<<_B as SymbolBuilder>::Prerequisites> - + CanHandle<<_L as ResourceLocator>::Prerequisites>, -{ - fn handle(&mut self, r: T) -> Result<(::Artifact, bool), Box> { - self.add(r) - } -} - -pub struct Setup< - SR, - L = DefaultLocator, - R = DefaultResources<'static, &'static str>, - B = DefaultBuilder, -> { - symbol_runner: SR, - resources: HashSet, - phantom: PhantomData<(L, B)>, -} - -// https://github.com/rust-lang/rust/issues/27336 -impl Setup { - pub fn new(symbol_runner: SR) -> Self { - Self { - symbol_runner, - resources: Default::default(), - phantom: Default::default(), - } - } -} - -impl Setup { - pub fn new_with(symbol_runner: SR) -> Self { - Self { - symbol_runner, - resources: Default::default(), - phantom: Default::default(), - } - } -} - -pub trait Runnable { - fn run(&self, runner: &R, force: bool) -> Result>; -} - -impl Runnable for S { - fn run(&self, runner: &R, force: bool) -> Result> { - runner.run_symbol(self, force) - } -} - -macro_rules! runnable_for_tuple { - ( $($name:ident)* ) => ( - #[allow(non_snake_case)] - impl<$($name: Symbol + Debug,)*> Runnable for ($($name,)*) { - #[allow(unused)] - fn run<_R: SymbolRunner>(&self, runner: &_R, force: bool) -> Result> { - let ($($name,)*) = self; - Ok($(runner.run_symbol($name, force)? || )* false) - } - } - ); -} - -for_each_tuple!(runnable_for_tuple); - -impl Setup { - pub fn add(&mut self, resource: R) -> Result<(R::Artifact, bool), Box> - where - B: SymbolBuilder, - >::Symbol: Runnable + Debug, - L: ResourceLocator, - Rs: From + BorrowResource, - Self: CanHandle + CanHandle<>::Prerequisites>, - { - self.add_force(resource, false) - } - - pub fn add_force( - &mut self, - resource: R, - force_run: bool, - ) -> Result<(R::Artifact, bool), Box> - where - B: SymbolBuilder, - >::Symbol: Runnable + Debug, - L: ResourceLocator, - Rs: From + BorrowResource, - Self: CanHandle + CanHandle<>::Prerequisites>, - { - let (target, target_prereqs) = L::locate(&resource); - let storable_resource = Rs::from(resource); - let did_run = if self.resources.get(&storable_resource).is_some() { - assert!( - !force_run, - "Forcing to run an already-added resource is a logical error" - ); - false - } else { - let (_, target_prereqs_did_run) = self.handle(target_prereqs)?; - let (symbol, prereqs_did_run) = - self.get_symbol(storable_resource.borrow_resource().unwrap(), &target)?; - self.resources.insert(storable_resource); - self.run_symbol( - symbol, - force_run || target_prereqs_did_run || prereqs_did_run, - )? - }; - Ok((target, did_run)) - } - - fn get_symbol( - &mut self, - resource: &R, - target: &R::Artifact, - ) -> Result<(>::Symbol, bool), Box> - where - B: SymbolBuilder, - Self: CanHandle, - { - let (prereqs, prereqs_did_run) = self.handle(B::prerequisites(resource))?; - Ok((B::create(resource, target, prereqs), prereqs_did_run)) - } - - pub fn run_symbol(&self, symbol: S, force: bool) -> Result> { - symbol.run(&self.symbol_runner, force) - } -} - -#[cfg(test)] -mod test { - use crate::resources::{BorrowResource, Resource}; - use crate::schema::SymbolRunner; - use crate::symbols::Symbol; - use crate::to_artifact::ToArtifact; - use crate::{ResourceLocator, Setup, SymbolBuilder}; - use std::cell::RefCell; - use std::error::Error; - use std::fmt::Debug; - use std::rc::Rc; - - struct TestSymbolRunner { - count: Rc>, - } - - impl SymbolRunner for TestSymbolRunner { - fn run_symbol( - &self, - symbol: &S, - force: bool, - ) -> Result> { - let run = force || !symbol.target_reached()?; - if run { - *self.count.borrow_mut() += 1; - } - Ok(run) - } - } - - #[derive(Debug, PartialEq, Eq, Hash)] - struct TestResource(&'static str, T); - impl Resource for TestResource { - type Artifact = (); - } - - #[derive(Debug, Hash, PartialEq, Eq)] - enum Resources { - A(TestResource<&'static str>), - B(TestResource<()>), - } - impl From> for Resources { - fn from(from: TestResource<&'static str>) -> Self { - Self::A(from) - } - } - impl From> for Resources { - fn from(from: TestResource<()>) -> Self { - Self::B(from) - } - } - - impl BorrowResource> for Resources { - fn borrow_resource(&self) -> Option<&TestResource<&'static str>> { - match self { - Self::A(a) => Some(a), - _ => None, - } - } - } - impl BorrowResource> for Resources { - fn borrow_resource(&self) -> Option<&TestResource<()>> { - match self { - Self::B(b) => Some(b), - _ => None, - } - } - } - - struct TestResourceLocator; - impl ResourceLocator> for TestResourceLocator { - type Prerequisites = (); - fn locate(_resource: &TestResource) -> ( as ToArtifact>::Artifact, ()) { - ((), ()) - } - } - - struct TestSymbolBuilder; - impl SymbolBuilder> for TestSymbolBuilder { - type Symbol = TestSymbol; - type Prerequisites = TestResource<()>; - - fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites { - TestResource(resource.1, ()) - } - fn create( - resource: &TestResource<&'static str>, - (): &(), - _inputs: ::Artifact, - ) -> Self::Symbol { - TestSymbol { - reached: resource.0.chars().next().unwrap().is_uppercase(), - } - } - } - impl SymbolBuilder> for TestSymbolBuilder { - type Symbol = TestSymbol; - type Prerequisites = (); - - fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {} - fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Symbol { - TestSymbol { - reached: resource.0.chars().next().unwrap().is_uppercase(), - } - } - } - - #[derive(Debug)] - struct TestSymbol { - reached: bool, - } - impl Symbol for TestSymbol { - fn target_reached(&self) -> Result> { - Ok(self.reached) - } - fn execute(&self) -> Result<(), Box> { - Ok(()) - } - } - - fn get_setup() -> ( - Rc>, - Setup, - ) { - let count = Rc::new(RefCell::new(0)); - let runner = TestSymbolRunner { - count: Rc::clone(&count), - }; - (count, Setup::new_with(runner)) - } - - #[test] - fn correctly_uses_force() { - let (count, mut setup) = get_setup(); - setup.add(TestResource("A", "b")).unwrap(); - assert_eq!(*count.borrow(), 2); - setup.add(TestResource("A", "b")).unwrap(); - assert_eq!(*count.borrow(), 2); - - let (count, mut setup) = get_setup(); - setup.add(TestResource("A", "B")).unwrap(); - assert_eq!(*count.borrow(), 0); - } -} diff --git a/src/static_files.rs b/src/static_files.rs deleted file mode 100644 index c2d580d..0000000 --- a/src/static_files.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/static_files.rs")); diff --git a/src/storage.rs b/src/storage.rs index a963414..ea22765 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,33 +1,32 @@ use std::error::Error; use std::fs::read_dir; -use std::path::PathBuf; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; pub trait Storage { - fn write_filename(&self) -> PathBuf; - fn read_filename(&self) -> Result>; + fn write_filename(&self) -> String; + fn read_filename(&self) -> Result>; fn recent_date(&self) -> Result>; } -#[derive(Debug, Clone)] -pub struct SimpleStorage(PathBuf); +#[derive(Clone)] +pub struct SimpleStorage(String, String); impl SimpleStorage { - pub const fn new(base: PathBuf) -> Self { - Self(base) + pub const fn new(base: String, filename: String) -> Self { + Self(base, filename) } - fn get_path(&self, date: Option) -> PathBuf { + fn get_path(&self, date: Option) -> String { match date { - Some(d) => self.0.join(d.to_string()), - None => self.0.clone(), + Some(d) => format!("{}/_{}/{}", self.0, self.1, d), + None => format!("{}/_{}", self.0, self.1), } } } impl Storage for SimpleStorage { - fn write_filename(&self) -> PathBuf { + fn write_filename(&self) -> String { self.get_path(Some( SystemTime::now() .duration_since(UNIX_EPOCH) @@ -36,7 +35,7 @@ impl Storage for SimpleStorage { )) } - fn read_filename(&self) -> Result> { + fn read_filename(&self) -> Result> { Ok(self.get_path(Some(self.recent_date()?))) } diff --git a/src/symbols/acme/account_key.rs b/src/symbols/acme/account_key.rs new file mode 100644 index 0000000..e2807a8 --- /dev/null +++ b/src/symbols/acme/account_key.rs @@ -0,0 +1,85 @@ +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, C: CommandRunner> { + path: P, + command_runner: &'a C, +} + +impl<'a, P: AsRef, 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, 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, C: CommandRunner> Symbol for AcmeAccountKey<'_, P, C> { + fn target_reached(&self) -> Result> { + 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> { + self.command_runner.run_successfully( + "openssl", + args![ + "genrsa", + "-out", + self.path.as_ref(), + self.get_bytes().to_string(), + ], + ) + } + + fn get_prerequisites(&self) -> Vec { + 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 { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +#[cfg(test)] +mod test {} diff --git a/src/symbols/acme/cert.rs b/src/symbols/acme/cert.rs index 9145301..2af81b4 100644 --- a/src/symbols/acme/cert.rs +++ b/src/symbols/acme/cert.rs @@ -1,61 +1,80 @@ -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::marker::PhantomData; -use std::path::Path; +use std::path::{Path, PathBuf}; -#[derive(Debug)] -pub struct Cert<_C, C, D, P> { +use crate::command_runner::CommandRunner; +use crate::resources::Resource; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct AcmeCert< + 'a, + D: AsRef, + R: AsRef, + C: CommandRunner, + K: AsRef, + CH: AsRef, +> { domain: D, - command_runner: C, - root_cert_path: P, - account_key_path: P, - challenges_path: P, - csr_path: P, - cert_path: P, - phantom: PhantomData<_C>, + command_runner: &'a C, + root_cert_path: R, + account_key_path: K, + challenges_path: CH, } -impl<_C, C, D, P> Cert<_C, C, D, P> { +impl<'a, D: AsRef, R: AsRef, C: CommandRunner, K: AsRef, CH: AsRef> + AcmeCert<'a, D, R, C, K, CH> +{ pub fn new( domain: D, - command_runner: C, - root_cert_path: P, - account_key_path: P, - challenges_path: P, - csr_path: P, - cert_path: P, + command_runner: &'a C, + root_cert_path: R, + account_key_path: K, + challenges_path: CH, ) -> Self { - Self { + AcmeCert { 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, R: AsRef, C: CommandRunner, K: AsRef, CH: AsRef> 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<_C: CommandRunner, C: Borrow<_C>, D: AsRef, P: AsRef> Symbol for Cert<_C, C, D, P> { +impl, R: AsRef, C: CommandRunner, K: AsRef, CH: AsRef> Symbol + for AcmeCert<'_, D, R, C, K, CH> +{ fn target_reached(&self) -> Result> { - if !self.cert_path.as_ref().exists() { + if !self.get_cert_path().exists() { return Ok(false); } - let output = self.command_runner.borrow().run_with_args( + let output = self.command_runner.run_with_args( "openssl", args![ "x509", "-in", - self.cert_path.as_ref(), + self.get_cert_path(), "-noout", "-subject", "-checkend", @@ -73,14 +92,13 @@ impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef, P: AsRef> Symbol for Ok( self .command_runner - .borrow() .run_successfully( "openssl", args![ "verify", "--untrusted", self.root_cert_path.as_ref(), - self.cert_path.as_ref(), + self.get_cert_path(), ], ) .is_ok(), @@ -100,21 +118,36 @@ impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef, P: AsRef> Symbol for } fn execute(&self) -> Result<(), Box> { - let output = self.command_runner.borrow().get_output( + let output = self.command_runner.get_output( "acme-tiny", args![ "--account-key", self.account_key_path.as_ref(), "--csr", - self.csr_path.as_ref(), + self.get_csr_path(), "--acme-dir", self.challenges_path.as_ref(), ], )?; - let mut file = FsFile::create(self.cert_path.as_ref())?; + let mut file = FsFile::create(self.get_cert_path())?; file.write_all(&output)?; Ok(()) } + + fn get_prerequisites(&self) -> Vec { + vec![Resource::new("file", self.get_csr_path().to_str().unwrap())] + } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } } #[cfg(test)] diff --git a/src/symbols/acme/chain.rs b/src/symbols/acme/chain.rs new file mode 100644 index 0000000..d62eb9b --- /dev/null +++ b/src/symbols/acme/chain.rs @@ -0,0 +1,117 @@ +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, R: AsRef, C: CommandRunner> { + domain: D, + command_runner: &'a C, + root_cert: R, +} + +impl<'a, D: AsRef, R: AsRef, 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, R: AsRef, 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, R: AsRef, C: CommandRunner> Symbol for AcmeCertChain<'_, D, R, C> { + fn target_reached(&self) -> Result> { + 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> { + 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 { + vec![Resource::new( + "file", + self.get_single_cert_path().to_str().unwrap(), + )] + } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +#[cfg(test)] +mod test {} diff --git a/src/symbols/acme/mod.rs b/src/symbols/acme/mod.rs index adf8941..c6390ef 100644 --- a/src/symbols/acme/mod.rs +++ b/src/symbols/acme/mod.rs @@ -1,2 +1,113 @@ +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; -pub use self::cert::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, H: AsRef, C: AsRef, 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, H: AsRef, C: AsRef, 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::() + .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>(&'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>( + &'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()), + ), + )) + } +} diff --git a/src/symbols/concat.rs b/src/symbols/concat.rs index cdde300..186dbcc 100644 --- a/src/symbols/concat.rs +++ b/src/symbols/concat.rs @@ -1,11 +1,13 @@ -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; -#[derive(Debug)] +use crate::resources::Resource; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + pub struct Concat { target: D, sources: S, @@ -44,4 +46,34 @@ impl, D: AsRef, I: AsRef> Symbol for Concat { } Ok(()) } + + fn get_prerequisites(&self) -> Vec { + let mut r: Vec = 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 { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box + where + Self: 'a, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl, I> fmt::Display for Concat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "Concat {}", self.target.as_ref().display()) + } } diff --git a/src/symbols/cron.rs b/src/symbols/cron.rs index e30978d..216b7cc 100644 --- a/src/symbols/cron.rs +++ b/src/symbols/cron.rs @@ -1,17 +1,19 @@ -use crate::command_runner::CommandRunner; -use crate::symbols::Symbol; use std::error::Error; +use std::fmt; -#[derive(Debug)] -pub struct Cron<'r, C, U, R> { +use crate::command_runner::CommandRunner; + +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct Cron<'r, C: AsRef, U: AsRef, R: CommandRunner> { user: U, content: C, command_runner: &'r R, } -impl<'r, U, R> Cron<'r, String, U, R> { +impl<'r, U: AsRef, R: CommandRunner> Cron<'r, String, U, R> { pub fn new>(user: U, content: C, command_runner: &'r R) -> Self { - Self { + Cron { user, content: String::from(content.as_ref()) + "\n", command_runner, @@ -38,4 +40,21 @@ impl, U: AsRef, R: CommandRunner> Symbol for Cron<'_, C, U, R } Ok(()) } + + fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box + where + Self: 'a, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl, U: AsRef, 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()) + } } diff --git a/src/symbols/dir.rs b/src/symbols/dir.rs index c97c12a..34d3757 100644 --- a/src/symbols/dir.rs +++ b/src/symbols/dir.rs @@ -1,21 +1,23 @@ -use crate::symbols::Symbol; use std::error::Error; +use std::fmt; use std::fs; use std::io; use std::path::Path; -#[derive(Debug)] -pub struct Dir

{ - path: P, +use crate::resources::Resource; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct Dir> { + path: D, } -impl

Dir

{ - pub fn new(path: P) -> Self { +impl> Dir { + pub fn new(path: D) -> Self { Self { path } } } -impl> Symbol for Dir

{ +impl> Symbol for Dir { fn target_reached(&self) -> Result> { if !self.path.as_ref().exists() { return Ok(false); @@ -33,4 +35,36 @@ impl> Symbol for Dir

{ fn execute(&self) -> Result<(), Box> { fs::create_dir(self.path.as_ref()).map_err(|e| Box::new(e) as Box) } + + fn get_prerequisites(&self) -> Vec { + if let Some(parent) = self.path.as_ref().parent() { + vec![Resource::new("dir", parent.to_str().unwrap())] + } else { + vec![] + } + } + + fn provides(&self) -> Option> { + Some(vec![Resource::new( + "dir", + self.path.as_ref().to_str().unwrap(), + )]) + } + + fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box + where + Self: 'a, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl> fmt::Display for Dir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "Dir {}", self.path.as_ref().display()) + } } diff --git a/src/symbols/factory.rs b/src/symbols/factory.rs new file mode 100644 index 0000000..6331ff7 --- /dev/null +++ b/src/symbols/factory.rs @@ -0,0 +1,436 @@ +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 + 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 + 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>( + &'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>( + &'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>( + &'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>( + &'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>( + &'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, F: 'a + AsRef>( + &'a self, + file: F, + user: U, + ) -> impl Symbol + 'a { + Owner::new(file, user, self.command_runner) + } + + pub fn get_file<'a, F: 'a + AsRef, Q: 'a + AsRef>( + &'a self, + path: Q, + content: F, + ) -> impl Symbol + 'a { + File::new(path, content) + } + + pub fn get_cron<'a, T: 'a + AsRef, U: 'a + AsRef>( + &'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 + 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 + 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>( + &'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>( + &'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), + )) + } +} diff --git a/src/symbols/file.rs b/src/symbols/file.rs index 25fe3fa..f6df2c0 100644 --- a/src/symbols/file.rs +++ b/src/symbols/file.rs @@ -1,22 +1,26 @@ -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; -#[derive(Debug)] -pub struct File { +use crate::resources::Resource; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct File, D: AsRef> { path: D, content: C, } -impl File { +impl, D: AsRef> File { pub fn new(path: D, content: C) -> Self { Self { path, content } } } -impl, C: AsRef> Symbol for File { +impl, D: AsRef> Symbol for File { fn target_reached(&self) -> Result> { if !self.path.as_ref().exists() { return Ok(false); @@ -40,4 +44,29 @@ impl, C: AsRef> Symbol for File { file.write_all(self.content.as_ref().as_bytes())?; Ok(()) } + + fn get_prerequisites(&self) -> Vec { + 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 { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box + where + Self: 'a, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl, D: AsRef> fmt::Display for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "File {}", self.path.as_ref().display()) + } } diff --git a/src/symbols/git/checkout.rs b/src/symbols/git/checkout.rs index 09a5a5a..b837061 100644 --- a/src/symbols/git/checkout.rs +++ b/src/symbols/git/checkout.rs @@ -1,48 +1,62 @@ -use crate::command_runner::CommandRunner; -use crate::symbols::Symbol; -use std::borrow::Borrow; use std::error::Error; use std::ffi::OsStr; -use std::marker::PhantomData; +use std::fmt; +use std::fs::metadata; +use std::io; use std::path::Path; -#[derive(Debug)] -pub struct Checkout<_C, C, P, S, B> { - target: P, - source: S, - branch: B, - command_runner: C, - phantom: PhantomData<_C>, +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> { + target: T, + source: &'a str, + branch: &'a str, + command_runner: &'a C, } -impl Checkout<_C, C, P, S, B> { - pub fn new(target: P, source: S, branch: B, command_runner: C) -> Self { - Self { +impl<'a, C: CommandRunner, T: AsRef> GitCheckout<'a, C, T> { + pub fn new(target: T, source: &'a str, branch: &'a str, command_runner: &'a C) -> Self { + GitCheckout { target, source, branch, command_runner, - phantom: PhantomData::default(), } } } -impl, P: AsRef, S, B> Checkout { - fn _run_in_target_repo(&self, args: &[impl AsRef]) -> Result, Box> { +impl> 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> GitCheckout<'_, C, T> { + fn _run_in_target_repo>(&self, args: &[S]) -> Result, Box> { 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.borrow().get_output("git", &new_args) + self.command_runner.get_output("git", &new_args) } } -impl, P: AsRef, S: AsRef, B: AsRef> Symbol - for Checkout -{ +impl> Symbol for GitCheckout<'_, C, T> { fn target_reached(&self) -> Result> { - if !self.target.as_ref().exists() { - return Ok(false); + if let Err(e) = metadata(self.target.as_ref()) { + return if e.kind() == io::ErrorKind::NotFound { + Ok(false) + } else { + Err(Box::new(e)) + }; } - self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?; + self._run_in_target_repo(&["fetch", self.source, self.branch])?; // 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"])?; @@ -51,23 +65,48 @@ impl, P: AsRef, S: AsRef, B: AsRef Result<(), Box> { if !self.target.as_ref().exists() { - return self.command_runner.borrow().run_successfully( + return self.command_runner.run_successfully( "git", - args![ - "clone", - "--depth", - "1", - "-b", + &[ + OsStr::new("clone"), + "--depth".as_ref(), + "1".as_ref(), + "-b".as_ref(), self.branch.as_ref(), self.source.as_ref(), - self.target.as_ref(), + self.target.as_ref().as_ref(), ], ); } - self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?; + self._run_in_target_repo(&["fetch", self.source, self.branch])?; self._run_in_target_repo(&["merge", "FETCH_HEAD"])?; Ok(()) } + + fn get_prerequisites(&self) -> Vec { + vec![Resource::new( + "dir", + self.target.as_ref().parent().unwrap().to_string_lossy(), + )] + } + + fn provides(&self) -> Option> { + Some(vec![Resource::new( + "dir", + self.target.as_ref().to_str().unwrap(), + )]) + } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } } #[cfg(test)] diff --git a/src/symbols/git/mod.rs b/src/symbols/git/mod.rs index 580afe8..d3e2df1 100644 --- a/src/symbols/git/mod.rs +++ b/src/symbols/git/mod.rs @@ -1,4 +1,2 @@ -mod checkout; -//pub mod submodules; - -pub use checkout::Checkout; +pub mod checkout; +pub mod submodules; diff --git a/src/symbols/hook.rs b/src/symbols/hook.rs new file mode 100644 index 0000000..3cd4519 --- /dev/null +++ b/src/symbols/hook.rs @@ -0,0 +1,196 @@ +use std::error::Error; +use std::fmt; + +use crate::resources::Resource; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct Hook +where + A: Symbol, + B: Symbol, +{ + a: A, + b: B, +} + +// A and B are executed if either A or B are not reached +impl Hook +where + A: Symbol, + B: Symbol, +{ + pub fn new(a: A, b: B) -> Self { + Self { a, b } + } +} + +impl Symbol for Hook +where + A: Symbol, + B: Symbol, +{ + fn target_reached(&self) -> Result> { + self.a.target_reached().and_then(|reached| { + if reached { + self.b.target_reached() + } else { + Ok(reached) + } + }) + } + + fn execute(&self) -> Result<(), Box> { + self.a.execute()?; + self.b.execute() + } + + fn get_prerequisites(&self) -> Vec { + 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> { + 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 { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box + where + Self: 'a, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl fmt::Display for Hook +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> { + Err(self.0.clone().into()) + } + fn execute(&self) -> Result<(), Box> { + Err(self.0.clone().into()) + } + fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box + 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> { + Ok(self.0) + } + fn execute(&self) -> Result<(), Box> { + Ok(()) + } + fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box + 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(), ()); + } +} diff --git a/src/symbols/list.rs b/src/symbols/list.rs new file mode 100644 index 0000000..2df3247 --- /dev/null +++ b/src/symbols/list.rs @@ -0,0 +1,213 @@ +use std::error::Error; +use std::fmt; + +use crate::resources::Resource; +use crate::symbols::{Action, Symbol, SymbolRunner}; + +pub struct List<'a> { + symbols: Vec>, +} + +impl<'a> List<'a> { + pub fn new(symbols: Vec>) -> Self { + List { symbols } + } +} + +impl Symbol for List<'_> { + fn target_reached(&self) -> Result> { + for symbol in &self.symbols { + if !symbol.target_reached()? { + return Ok(false); + } + } + Ok(true) + } + + fn execute(&self) -> Result<(), Box> { + for symbol in &self.symbols { + symbol.execute()?; + } + Ok(()) + } + + fn get_prerequisites(&self) -> Vec { + 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> { + 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 { + Box::new(SymbolListAction::new(runner, &self.symbols)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + 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], +} + +impl<'a> SymbolListAction<'a> { + fn new(runner: &'a dyn SymbolRunner, symbols: &'a [Box]) -> Self { + Self { runner, symbols } + } +} + +impl Action for SymbolListAction<'_> { + fn run(&self) -> Result<(), Box> { + for symbol in self.symbols { + symbol.as_action(self.runner).run()?; + } + Ok(()) + } +} + +pub struct ListAction<'a> { + actions: Vec>, +} + +impl<'a> ListAction<'a> { + pub fn new(actions: Vec>) -> Self { + Self { actions } + } +} + +impl Action for ListAction<'_> { + fn run(&self) -> Result<(), Box> { + 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> { Err(self.0.clone().into()) } + fn execute(&self) -> Result<(), Box> { 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> { Ok(self.0) } + fn execute(&self) -> Result<(), Box> { 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(), ()); + } +} +*/ diff --git a/src/symbols/mariadb/database.rs b/src/symbols/mariadb/database.rs index 4a35ae2..2d44165 100644 --- a/src/symbols/mariadb/database.rs +++ b/src/symbols/mariadb/database.rs @@ -1,18 +1,19 @@ -use crate::command_runner::CommandRunner; -use crate::storage::Storage; -use crate::symbols::Symbol; use std::error::Error; +use std::fmt; +use std::path::Path; -#[derive(Debug)] -pub struct Database<'a, D, S, C> { +use crate::command_runner::CommandRunner; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct MariaDBDatabase<'a, D: AsRef, S: AsRef, C: CommandRunner> { db_name: D, seed_file: S, command_runner: &'a C, } -impl<'a, D, S, C: CommandRunner> Database<'a, D, S, C> { +impl<'a, D: AsRef, S: AsRef, C: CommandRunner> MariaDBDatabase<'a, D, S, C> { pub fn new(db_name: D, seed_file: S, command_runner: &'a C) -> Self { - Self { + MariaDBDatabase { db_name, seed_file, command_runner, @@ -27,7 +28,15 @@ impl<'a, D, S, C: CommandRunner> Database<'a, D, S, C> { } } -impl, S: Storage, C: CommandRunner> Symbol for Database<'_, D, S, C> { +impl, S: AsRef, 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, S: AsRef, C: CommandRunner> Symbol for MariaDBDatabase<'_, D, S, C> { fn target_reached(&self) -> Result> { Ok( self @@ -46,11 +55,22 @@ impl, S: Storage, C: CommandRunner> Symbol for Database<'_, D, S, format!( "mariadb '{}' < {}", self.db_name.as_ref(), - self.seed_file.read_filename()?.to_str().unwrap() + self.seed_file.as_ref().to_str().unwrap() ), ], ) } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } } #[cfg(test)] diff --git a/src/symbols/mariadb/database_dump.rs b/src/symbols/mariadb/database_dump.rs new file mode 100644 index 0000000..355ad1e --- /dev/null +++ b/src/symbols/mariadb/database_dump.rs @@ -0,0 +1,75 @@ +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, C: CommandRunner, S: Storage> { + db_name: N, + storage: S, + command_runner: &'a C, +} + +impl<'a, N: AsRef, 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> { + let b = self + .command_runner + .get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?; + Ok(String::from_utf8(b)?) + } +} + +impl, 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, C: CommandRunner, S: Storage> Symbol for DatabaseDump<'_, N, C, S> { + fn target_reached(&self) -> Result> { + 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> { + 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 { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +#[cfg(test)] +mod test {} diff --git a/src/symbols/mariadb/dump.rs b/src/symbols/mariadb/dump.rs deleted file mode 100644 index e01d9d8..0000000 --- a/src/symbols/mariadb/dump.rs +++ /dev/null @@ -1,56 +0,0 @@ -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> { - let b = self - .command_runner - .get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?; - Ok(String::from_utf8(b)?) - } -} - -impl, C: CommandRunner, S: Storage> Symbol for Dump<'_, N, C, S> { - fn target_reached(&self) -> Result> { - 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> { - 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 {} diff --git a/src/symbols/mariadb/mod.rs b/src/symbols/mariadb/mod.rs index 46aec7d..584a74c 100644 --- a/src/symbols/mariadb/mod.rs +++ b/src/symbols/mariadb/mod.rs @@ -1,7 +1,7 @@ mod database; -mod dump; +mod database_dump; mod user; -pub use self::database::Database; -pub use self::dump::Dump; -pub use self::user::User; +pub use self::database::MariaDBDatabase; +pub use self::database_dump::DatabaseDump; +pub use self::user::MariaDBUser; diff --git a/src/symbols/mariadb/user.rs b/src/symbols/mariadb/user.rs index 337f229..0037633 100644 --- a/src/symbols/mariadb/user.rs +++ b/src/symbols/mariadb/user.rs @@ -1,16 +1,18 @@ -use crate::command_runner::CommandRunner; -use crate::symbols::Symbol; use std::error::Error; +use std::fmt; -#[derive(Debug)] -pub struct User<'a, U, C> { +use crate::command_runner::CommandRunner; +use crate::resources::Resource; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct MariaDBUser<'a, U: AsRef, C: CommandRunner> { user_name: U, command_runner: &'a C, } -impl<'a, U: AsRef, C: CommandRunner> User<'a, U, C> { +impl<'a, U: AsRef, C: CommandRunner> MariaDBUser<'a, U, C> { pub fn new(user_name: U, command_runner: &'a C) -> Self { - Self { + MariaDBUser { user_name, command_runner, } @@ -24,7 +26,13 @@ impl<'a, U: AsRef, C: CommandRunner> User<'a, U, C> { } } -impl, C: CommandRunner> Symbol for User<'_, U, C> { +impl, 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, C: CommandRunner> Symbol for MariaDBUser<'_, U, C> { fn target_reached(&self) -> Result> { Ok( self @@ -44,6 +52,21 @@ impl, C: CommandRunner> Symbol for User<'_, U, C> { ))?; Ok(()) } + + fn get_prerequisites(&self) -> Vec { + vec![Resource::new("user", self.user_name.as_ref())] + } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } } #[cfg(test)] diff --git a/src/symbols/mod.rs b/src/symbols/mod.rs index a9932a8..779d186 100644 --- a/src/symbols/mod.rs +++ b/src/symbols/mod.rs @@ -1,22 +1,88 @@ +use crate::resources::Resource; use std::error::Error; +use std::fmt::Display; + +pub trait Action { + fn run(&self) -> Result<(), Box>; +} + +pub trait SymbolRunner { + fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box>; +} + +impl SymbolRunner for Box { + fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { + (**self).run_symbol(symbol) + } +} // Symbol -pub trait Symbol { +pub trait Symbol: Display { fn target_reached(&self) -> Result>; fn execute(&self) -> Result<(), Box>; + fn get_prerequisites(&self) -> Vec { + vec![] + } + fn provides(&self) -> Option> { + None + } + fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box; + fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box + 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 Action for SymbolAction<'_, S> { + fn run(&self) -> Result<(), Box> { + 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> { + 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 saved_directory; +pub mod stored_directory; pub mod systemd; pub mod tls; pub mod user; diff --git a/src/symbols/nginx/mod.rs b/src/symbols/nginx/mod.rs new file mode 100644 index 0000000..74f47ad --- /dev/null +++ b/src/symbols/nginx/mod.rs @@ -0,0 +1 @@ +pub mod server; diff --git a/src/symbols/nginx/server.rs b/src/symbols/nginx/server.rs new file mode 100644 index 0000000..dff4182 --- /dev/null +++ b/src/symbols/nginx/server.rs @@ -0,0 +1,246 @@ +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 { + ExecError(E), + GenericError, +} + +impl From for NginxServerError { + fn from(err: io::Error) -> Self { + Self::ExecError(err) + } +} + +impl Error for NginxServerError { + 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 fmt::Display for NginxServerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", self.description()) + } +} + +pub struct NginxServer<'a, C: CommandRunner, T: AsRef, P: AsRef> { + command_runner: &'a C, + file: FileSymbol, +} + +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, STATIC: AsRef>( + 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 SocketSpec for T +where + T: AsRef, +{ + 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>( + 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, STATIC: AsRef>( + 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>( + 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, P: AsRef> Symbol for NginxServer<'_, C, T, P> { + fn target_reached(&self) -> Result> { + 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> { + self.file.execute()?; + self + .command_runner + .run_successfully("systemctl", args!["reload-or-restart", "nginx"]) + } + + fn get_prerequisites(&self) -> Vec { + self.file.get_prerequisites() + } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl, P: AsRef> fmt::Display for NginxServer<'_, C, T, P> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "Nginx server config") + } +} diff --git a/src/symbols/noop.rs b/src/symbols/noop.rs new file mode 100644 index 0000000..32943d5 --- /dev/null +++ b/src/symbols/noop.rs @@ -0,0 +1,31 @@ +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> { + Ok(true) + } + fn execute(&self) -> Result<(), Box> { + Ok(()) + } + fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box + 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") + } +} diff --git a/src/symbols/npm.rs b/src/symbols/npm.rs index afc42ba..e9d79ed 100644 --- a/src/symbols/npm.rs +++ b/src/symbols/npm.rs @@ -1,25 +1,25 @@ -use crate::command_runner::CommandRunner; -use crate::symbols::Symbol; use std::error::Error; use std::fmt; use std::path::Path; -#[derive(Debug)] -pub struct Install<'a, T: AsRef, C: CommandRunner> { +use crate::command_runner::CommandRunner; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct NpmInstall<'a, T: AsRef, C: CommandRunner> { target: T, command_runner: &'a C, } -impl<'a, T: AsRef, C: CommandRunner> Install<'a, T, C> { +impl<'a, T: AsRef, C: CommandRunner> NpmInstall<'a, T, C> { pub fn new(target: T, command_runner: &'a C) -> Self { - Install { + NpmInstall { target, command_runner, } } } -impl, C: CommandRunner> fmt::Display for Install<'_, T, C> { +impl, C: CommandRunner> fmt::Display for NpmInstall<'_, T, C> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -29,7 +29,7 @@ impl, C: CommandRunner> fmt::Display for Install<'_, T, C> { } } -impl, C: CommandRunner> Symbol for Install<'_, T, C> { +impl, C: CommandRunner> Symbol for NpmInstall<'_, T, C> { fn target_reached(&self) -> Result> { if !self.target.as_ref().exists() { return Ok(false); @@ -61,6 +61,17 @@ impl, C: CommandRunner> Symbol for Install<'_, T, C> { ], ) } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } } #[cfg(test)] diff --git a/src/symbols/owner.rs b/src/symbols/owner.rs index a58c4fc..eed6b92 100644 --- a/src/symbols/owner.rs +++ b/src/symbols/owner.rs @@ -1,37 +1,35 @@ -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; -#[derive(Debug)] -pub struct Owner<_C, C, P, U> { - path: P, +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, U: AsRef> { + path: D, user_name: U, - command_runner: C, - phantom: PhantomData<_C>, + command_runner: &'a C, } -impl<_C, C, P, U> Owner<_C, C, P, U> { - pub fn new(path: P, user_name: U, command_runner: C) -> Self { - Self { +impl<'a, C: CommandRunner, D: AsRef, U: AsRef> Owner<'a, C, D, U> { + pub fn new(path: D, user_name: U, command_runner: &'a C) -> Self { + Owner { path, user_name, command_runner, - phantom: PhantomData::default(), } } } -impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, U: AsRef> Symbol - for Owner<_C, C, P, U> -{ +impl, U: AsRef> Symbol for Owner<'_, C, D, U> { fn target_reached(&self) -> Result> { - if !self.path.as_ref().exists() { + if !Path::new(self.path.as_ref()).exists() { return Ok(false); } let actual_uid = fs::metadata(self.path.as_ref()).unwrap().uid(); @@ -40,9 +38,35 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, U: AsRef> Symbol } fn execute(&self) -> Result<(), Box> { - self.command_runner.borrow().run_successfully( - "chown", - args!["-R", self.user_name.as_ref(), self.path.as_ref()], + 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 { + vec![Resource::new("user", self.user_name.as_ref())] + } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl, U: AsRef> 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() ) } } diff --git a/src/symbols/postgresql/database.rs b/src/symbols/postgresql/database.rs index 7bc6c88..1ef3728 100644 --- a/src/symbols/postgresql/database.rs +++ b/src/symbols/postgresql/database.rs @@ -1,8 +1,9 @@ -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, S: AsRef, C: CommandRunner> { name: N, seed_file: S, @@ -84,6 +85,17 @@ impl, S: AsRef, C: CommandRunner> Symbol for PostgreSQLDataba ], ) } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } } #[cfg(test)] diff --git a/src/symbols/saved_directory.rs b/src/symbols/stored_directory.rs similarity index 52% rename from src/symbols/saved_directory.rs rename to src/symbols/stored_directory.rs index 81772bc..827e807 100644 --- a/src/symbols/saved_directory.rs +++ b/src/symbols/stored_directory.rs @@ -1,50 +1,57 @@ -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, - Store, + Save, } -#[derive(Debug)] -pub struct SavedDirectory<_C, C, P, S> { +pub struct StoredDirectory<'a, P: AsRef, S: Storage, C: CommandRunner> { path: P, storage: S, dir: StorageDirection, - command_runner: C, - phantom: PhantomData<_C>, + command_runner: &'a C, } -impl<_C, C, P, S> SavedDirectory<_C, C, P, S> { - pub fn new(path: P, storage: S, dir: StorageDirection, command_runner: C) -> Self { - Self { +impl<'a, P: AsRef, S: Storage, C: CommandRunner> StoredDirectory<'a, P, S, C> { + pub fn new(path: P, storage: S, dir: StorageDirection, command_runner: &'a C) -> Self { + StoredDirectory { path, storage, dir, command_runner, - phantom: PhantomData::default(), } } } -impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: Storage> Symbol - for SavedDirectory<_C, C, P, S> -{ +impl, 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, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_, P, S, C> { fn target_reached(&self) -> Result> { 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::Store) + Ok(self.dir == StorageDirection::Save) } else { Err(Box::new(e)) }; @@ -57,7 +64,7 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: Storage> Symbol } let dump_date = self.storage.recent_date()?; - let output = self.command_runner.borrow().get_output( + let output = self.command_runner.get_output( "sh", args![ "-c", @@ -68,12 +75,12 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: Storage> Symbol ], )?; let modified_date = u64::from_str(String::from_utf8(output)?.trim_end())?; - if if self.dir == StorageDirection::Store { + if if self.dir == StorageDirection::Save { modified_date > dump_date } else { dump_date > modified_date } { - let output = self.command_runner.borrow().run_with_args( + let output = self.command_runner.run_with_args( "diff", args!["-rq", self.storage.read_filename()?, self.path.as_ref()], )?; @@ -91,19 +98,51 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: Storage> Symbol if self.dir == StorageDirection::Load { self .command_runner - .borrow() .run_successfully("rm", args!["-rf", self.path.as_ref()])?; - self.command_runner.borrow().run_successfully( + self.command_runner.run_successfully( "cp", args!["-a", self.storage.read_filename()?, self.path.as_ref()], ) } else { - self.command_runner.borrow().run_successfully( + self.command_runner.run_successfully( "cp", args!["-a", self.path.as_ref(), self.storage.write_filename()], ) } } + + fn get_prerequisites(&self) -> Vec { + 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> { + 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 { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } } #[cfg(test)] diff --git a/src/symbols/systemd/mod.rs b/src/symbols/systemd/mod.rs index 48167e6..687e04d 100644 --- a/src/symbols/systemd/mod.rs +++ b/src/symbols/systemd/mod.rs @@ -1,7 +1,3 @@ -mod reload; -mod user_service; -mod user_session; - -pub use reload::ReloadService; -pub use user_service::UserService; -pub use user_session::UserSession; +pub mod reload; +pub mod user_service; +pub mod user_session; diff --git a/src/symbols/systemd/reload.rs b/src/symbols/systemd/reload.rs index 36ea5ac..cc1262c 100644 --- a/src/symbols/systemd/reload.rs +++ b/src/symbols/systemd/reload.rs @@ -1,35 +1,49 @@ -use crate::command_runner::CommandRunner; -use crate::symbols::Symbol; -use std::borrow::Borrow; use std::error::Error; -use std::marker::PhantomData; +use std::fmt; -#[derive(Debug)] -pub struct ReloadService<_C, C, S> { +use crate::command_runner::CommandRunner; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct ReloadService<'a, S, C: CommandRunner> { service: S, - command_runner: C, - phantom: PhantomData<_C>, + command_runner: &'a C, } -impl<_C, C, S> ReloadService<_C, C, S> { - pub fn new(command_runner: C, service: S) -> Self { - Self { +impl<'a, S, C: CommandRunner> ReloadService<'a, S, C> { + pub fn new(service: S, command_runner: &'a C) -> Self { + ReloadService { service, command_runner, - phantom: PhantomData::default(), } } } -impl, _C: CommandRunner, C: Borrow<_C>> Symbol for ReloadService<_C, C, S> { +impl, C: CommandRunner> Symbol for ReloadService<'_, S, C> { fn target_reached(&self) -> Result> { Ok(true) } fn execute(&self) -> Result<(), Box> { - self.command_runner.borrow().run_successfully( + self.command_runner.run_successfully( "systemctl", args!["reload-or-restart", self.service.as_ref()], ) } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl, 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()) + } } diff --git a/src/symbols/systemd/user_service.rs b/src/symbols/systemd/user_service.rs index 5f2b31b..9d9eff0 100644 --- a/src/symbols/systemd/user_service.rs +++ b/src/symbols/systemd/user_service.rs @@ -1,34 +1,132 @@ -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 struct UserService<'a, S: AsRef, U: AsRef, R: CommandRunner> { - socket_path: S, - service_name: &'a str, - command_runner: SetuidCommandRunner<'a, U, R>, +pub enum UserServiceError { + ActivationFailed(io::Result), + ExecError(E), + GenericError, } -impl, U: AsRef, R: CommandRunner> UserService<'static, S, U, R> { - pub fn new( - socket_path: S, - user_name: U, - service_name: &'static str, - command_runner: &'static R, - ) -> Self { - Self { - socket_path, - service_name, - command_runner: SetuidCommandRunner::new(user_name, command_runner), +impl From for UserServiceError { + fn from(err: io::Error) -> Self { + Self::ExecError(err) + } +} + +impl Error for UserServiceError { + 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, U: AsRef, R: CommandRunner> UserService<'_, S, U, R> { +impl fmt::Display for UserServiceError { + 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, U: AsRef, C: AsRef, R: CommandRunner> { + socket_path: S, + service_name: &'a str, + user_name: U, + command_runner: R, + config: FileSymbol, +} + +impl<'a, S: AsRef, U: AsRef + 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, + ) + } + + pub fn new( + socket_path: S, + home: &'_ Path, + user_name: U, + service_name: &'a str, + command_runner: &'a R, + content: String, + ) -> Self { + let config_path: PathBuf = [ + home, + format!(".config/systemd/user/{}.service", service_name).as_ref(), + ] + .iter() + .collect(); + + UserService { + socket_path, + service_name, + user_name: user_name.clone(), + command_runner: SetuidCommandRunner::new(user_name, command_runner), + config: FileSymbol::new(config_path, content), + } + } +} + +impl, U: AsRef, C: AsRef, R: CommandRunner> UserService<'_, S, U, C, R> { fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result> { let mut tries = 5; loop { @@ -63,13 +161,12 @@ impl, U: AsRef, R: CommandRunner> UserService<'_, S, U, R> { "ActiveState=activating" => sleep(Duration::from_millis(500)), "ActiveState=active" => return Ok(true), "ActiveState=failed" => { - return Err( - String::from_utf8(self.command_runner.get_output( + return Err(Box::new( + UserServiceError::ActivationFailed(self.command_runner.run_with_args( "journalctl", args!["--user", format!("--user-unit={}", self.service_name)], - )?)? - .into(), - ) + )) as UserServiceError, + )) } _ => return Ok(false), } @@ -77,18 +174,26 @@ impl, U: AsRef, R: CommandRunner> UserService<'_, S, U, R> { } } -impl, U: AsRef, R: CommandRunner> Symbol for UserService<'_, S, U, R> { +impl, U: AsRef, C: AsRef, R: CommandRunner> Symbol + for UserService<'_, S, U, C, R> +{ fn target_reached(&self) -> Result> { + if !(self.config.target_reached()?) { + return Ok(false); + } self.check_if_service() } fn execute(&self) -> Result<(), Box> { + 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("Generic error".into()); + return Err(Box::new( + UserServiceError::GenericError as UserServiceError, + )); } if self.socket_path.as_ref().exists() { @@ -97,4 +202,32 @@ impl, U: AsRef, R: CommandRunner> Symbol for UserService<'_, sleep(Duration::from_millis(500)); } } + + fn get_prerequisites(&self) -> Vec { + 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 { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl, U: AsRef, C: AsRef, 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) + } } diff --git a/src/symbols/systemd/user_session.rs b/src/symbols/systemd/user_session.rs index c186ee9..496d08e 100644 --- a/src/symbols/systemd/user_session.rs +++ b/src/symbols/systemd/user_session.rs @@ -1,26 +1,55 @@ -use crate::command_runner::CommandRunner; -use crate::symbols::Symbol; use std::error::Error; -use std::path::Path; +use std::fmt; +use std::path::PathBuf; + +use crate::command_runner::CommandRunner; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; #[derive(Debug)] -pub struct UserSession<'a, U, C> { +pub enum SystemdUserSessionError { + ExecError(E), + GenericError, +} + +impl Error for SystemdUserSessionError { + 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 fmt::Display for SystemdUserSessionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", self.description()) + } +} + +pub struct SystemdUserSession<'a, U: AsRef, C: CommandRunner> { user_name: U, command_runner: &'a C, } -impl<'a, U, C> UserSession<'a, U, C> { +impl<'a, U: AsRef, C: CommandRunner> SystemdUserSession<'a, U, C> { pub fn new(user_name: U, command_runner: &'a C) -> Self { - Self { + SystemdUserSession { user_name, command_runner, } } } -impl, C: CommandRunner> Symbol for UserSession<'_, U, C> { +impl, C: CommandRunner> Symbol for SystemdUserSession<'_, U, C> { fn target_reached(&self) -> Result> { - let path = Path::new("/var/lib/systemd/linger").join(self.user_name.as_ref()); + let mut path = PathBuf::from("/var/lib/systemd/linger"); + path.push(self.user_name.as_ref()); Ok(path.exists()) // Could also do `loginctl show-user ${self.user_name} | grep -F 'Linger=yes` } @@ -30,4 +59,21 @@ impl, C: CommandRunner> Symbol for UserSession<'_, 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 { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl, 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()) + } } diff --git a/src/symbols/tls/csr.rs b/src/symbols/tls/csr.rs index e460ea0..3b81566 100644 --- a/src/symbols/tls/csr.rs +++ b/src/symbols/tls/csr.rs @@ -1,39 +1,56 @@ -use crate::command_runner::CommandRunner; -use crate::symbols::Symbol; -use std::borrow::Borrow; +use std::borrow::Cow; use std::error::Error; -use std::path::Path; +use std::ffi::OsStr; +use std::fmt; +use std::path::PathBuf; -#[derive(Debug)] -pub struct Csr { - command_runner: C, - domain: D, - key_path: K, - csr_path: P, +use crate::command_runner::CommandRunner; +use crate::resources::Resource; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct TlsCsr<'a, C: CommandRunner> { + domain: Cow<'a, str>, + command_runner: &'a C, } -impl Csr { - pub fn new(command_runner: C, domain: D, key_path: K, csr_path: P) -> Self { - Self { - command_runner, +impl<'a, C: CommandRunner> TlsCsr<'a, C> { + pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self { + TlsCsr { domain, - key_path, - csr_path, + command_runner, } } + + 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, K: Borrow, P: Borrow> Symbol - for Csr -{ +impl fmt::Display for TlsCsr<'_, C> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TlsCsr {}", self.domain) + } +} + +impl Symbol for TlsCsr<'_, C> { fn target_reached(&self) -> Result> { - if !self.csr_path.borrow().exists() { + if !self.get_csr_path().exists() { return Ok(false); } let output = self.command_runner.get_stderr( "openssl", - args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",], + &[ + OsStr::new("req"), + "-in".as_ref(), + self.get_csr_path().as_ref(), + "-noout".as_ref(), + "-verify".as_ref(), + ], )?; Ok(output == b"verify OK\n") } @@ -41,20 +58,35 @@ impl, K: Borrow, P: Borrow> Symbol fn execute(&self) -> Result<(), Box> { self.command_runner.run_successfully( "openssl", - args![ - "req", - "-new", - "-sha256", - "-key", - self.key_path.borrow(), - "-out", - self.csr_path.borrow(), - "-subj", - format!("/CN={}", self.domain.borrow()), + &[ + 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(), ], )?; Ok(()) } + + fn get_prerequisites(&self) -> Vec { + vec![Resource::new("file", self.get_key_path().to_str().unwrap())] + } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } } #[cfg(test)] diff --git a/src/symbols/tls/key.rs b/src/symbols/tls/key.rs index 4caff26..70a9b1b 100644 --- a/src/symbols/tls/key.rs +++ b/src/symbols/tls/key.rs @@ -1,42 +1,57 @@ -use crate::command_runner::CommandRunner; -use crate::symbols::Symbol; +use std::borrow::Cow; use std::error::Error; -use std::path::Path; +use std::ffi::OsStr; +use std::fmt; +use std::path::PathBuf; -#[derive(Debug)] -pub struct Key { - file_path: P, - command_runner: C, +use crate::command_runner::CommandRunner; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct TlsKey<'a, C: CommandRunner> { + domain: Cow<'a, str>, + command_runner: &'a C, } -impl Key { - pub fn new(command_runner: C, file_path: P) -> Self { - Self { - file_path, +impl<'a, C: CommandRunner> TlsKey<'a, C> { + pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self { + TlsKey { + domain, command_runner, } } + fn get_path(&self) -> PathBuf { + ["/etc/ssl/private", &format!("{}.key", self.domain)] + .iter() + .collect() + } + fn get_bytes(&self) -> u32 { 4096 } } -impl> Symbol for Key { +impl fmt::Display for TlsKey<'_, C> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TlsKey {}", self.domain) + } +} + +impl Symbol for TlsKey<'_, C> { fn target_reached(&self) -> Result> { - if !self.file_path.as_ref().exists() { + if !self.get_path().exists() { return Ok(false); } let stdout = self.command_runner.get_output( "openssl", - args![ - "rsa", - "-in", - self.file_path.as_ref(), - "-noout", - "-check", - "-text", + &[ + OsStr::new("rsa"), + "-in".as_ref(), + self.get_path().as_ref(), + "-noout".as_ref(), + "-check".as_ref(), + "-text".as_ref(), ], )?; // FIXME check bytes @@ -46,14 +61,25 @@ impl> Symbol for Key { fn execute(&self) -> Result<(), Box> { self.command_runner.run_successfully( "openssl", - args![ - "genrsa", - "-out", - self.file_path.as_ref(), - self.get_bytes().to_string(), + &[ + OsStr::new("genrsa"), + "-out".as_ref(), + self.get_path().as_ref(), + self.get_bytes().to_string().as_ref(), ], ) } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } } #[cfg(test)] diff --git a/src/symbols/tls/mod.rs b/src/symbols/tls/mod.rs index b5d0041..d7b4c79 100644 --- a/src/symbols/tls/mod.rs +++ b/src/symbols/tls/mod.rs @@ -1,5 +1,7 @@ mod csr; mod key; +mod self_signed_cert; -pub use self::csr::Csr; -pub use self::key::Key; +pub use self::csr::TlsCsr; +pub use self::key::TlsKey; +pub use self::self_signed_cert::SelfSignedTlsCert; diff --git a/src/symbols/tls/self_signed_cert.rs b/src/symbols/tls/self_signed_cert.rs new file mode 100644 index 0000000..71c0c4a --- /dev/null +++ b/src/symbols/tls/self_signed_cert.rs @@ -0,0 +1,119 @@ +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, C: CommandRunner> { + domain: D, + command_runner: &'a C, +} + +impl<'a, D: AsRef, 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, 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, C: CommandRunner> Symbol for SelfSignedTlsCert<'_, D, C> { + fn target_reached(&self) -> Result> { + 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> { + 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 { + vec![Resource::new("file", self.get_key_path().to_str().unwrap())] + } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +#[cfg(test)] +mod test {} diff --git a/src/symbols/user.rs b/src/symbols/user.rs index a4d7ddd..e3ca95d 100644 --- a/src/symbols/user.rs +++ b/src/symbols/user.rs @@ -1,23 +1,97 @@ -use crate::command_runner::CommandRunner; -use crate::symbols::Symbol; +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}; #[derive(Debug)] -pub struct User { - user_name: U, - command_runner: C, +pub enum UserAdderError { + AlreadyExists, + UnknownError, + ImplError(Box), } -impl User { - pub fn new(user_name: U, command_runner: C) -> Self { - Self { - user_name, - command_runner, +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, C: CommandRunner> Symbol for User { +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 { + 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> { fn target_reached(&self) -> Result> { let output = self .command_runner @@ -25,34 +99,108 @@ impl, C: CommandRunner> Symbol for User { match output.status.code() { Some(2) => Ok(false), Some(0) => Ok(true), - _ => Err("Unknown error".into()), + _ => Err(Box::new(UserError::GenericError)), } } fn execute(&self) -> Result<(), Box> { - self.command_runner.run_successfully( + self + .user_adder + .add_user(self.user_name.as_ref()) + .map_err(|e| Box::new(e) as Box) + } + + fn provides(&self) -> Option> { + Some(vec![Resource::new("user", self.user_name.to_owned())]) + } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + 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 UserAdder for SystemUserAdder<'_, C> { + fn add_user(&self, user_name: &str) -> Result<(), UserAdderError> { + let output = self.command_runner.run_with_args( "adduser", args![ // "-m", // Necessary for Fedora, not accepted in Debian - "--system", - self.user_name.as_ref(), + "--system", user_name, ], - )?; - Ok(()) + ); + 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))), + } } } #[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", - command_runner: StdCommandRunner, + user_name: "nonexisting".into(), + command_runner: &StdCommandRunner, + user_adder: &DummyUserAdder, }; assert_eq!(symbol.target_reached().unwrap(), false); } @@ -60,8 +208,9 @@ mod test { #[test] fn test_target_reached_root() { let symbol = User { - user_name: "root", - command_runner: StdCommandRunner, + user_name: "root".into(), + command_runner: &StdCommandRunner, + user_adder: &DummyUserAdder, }; assert_eq!(symbol.target_reached().unwrap(), true); } diff --git a/src/symbols/wordpress/mod.rs b/src/symbols/wordpress/mod.rs index c2d705a..e489e42 100644 --- a/src/symbols/wordpress/mod.rs +++ b/src/symbols/wordpress/mod.rs @@ -1,5 +1,2 @@ -mod plugin; -mod translation; - -pub use plugin::Plugin; -pub use translation::Translation; +pub mod plugin; +pub mod translation; diff --git a/src/symbols/wordpress/plugin.rs b/src/symbols/wordpress/plugin.rs index 123346c..1bdb1c9 100644 --- a/src/symbols/wordpress/plugin.rs +++ b/src/symbols/wordpress/plugin.rs @@ -1,23 +1,24 @@ 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::symbols::Symbol; +use crate::resources::Resource; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; -#[derive(Debug)] -pub struct Plugin<'a, P, N, R> { +pub struct WordpressPlugin<'a, P: AsRef, N: AsRef, R: CommandRunner> { base: P, name: N, command_runner: &'a R, } -impl<'a, P: AsRef, N: AsRef, R: CommandRunner> Plugin<'a, P, N, R> { +impl<'a, P: AsRef, N: AsRef, R: CommandRunner> WordpressPlugin<'a, P, N, R> { pub fn new(base: P, name: N, command_runner: &'a R) -> Self { - Self { + WordpressPlugin { base, name, command_runner, @@ -33,7 +34,7 @@ impl<'a, P: AsRef, N: AsRef, R: CommandRunner> Plugin<'a, P, N, R> { } } -impl, N: AsRef, R: CommandRunner> Symbol for Plugin<'_, P, N, R> { +impl, N: AsRef, R: CommandRunner> Symbol for WordpressPlugin<'_, P, N, R> { fn target_reached(&self) -> Result> { let base_path = self.get_path(); if !base_path.exists() { @@ -98,4 +99,30 @@ impl, N: AsRef, R: CommandRunner> Symbol for Plugin<'_, P, N args![zip, "-d", self.base.as_ref().join("wp-content/plugins")], ) } + + fn get_prerequisites(&self) -> Vec { + 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 { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl, N: AsRef, 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()) + } } diff --git a/src/symbols/wordpress/translation.rs b/src/symbols/wordpress/translation.rs index 15dacd2..ea1aea2 100644 --- a/src/symbols/wordpress/translation.rs +++ b/src/symbols/wordpress/translation.rs @@ -1,26 +1,31 @@ -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; -#[derive(Debug)] -pub struct Translation<'a, C, D, R> { +use crate::command_runner::CommandRunner; +use crate::resources::Resource; +use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; + +pub struct WordpressTranslation<'a, C: AsRef, D: AsRef, R: CommandRunner> { path: D, version: &'a str, locale: C, command_runner: &'a R, } -impl<'a, D, C: AsRef, R: CommandRunner> Translation<'a, C, D, R> { - pub fn new(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self { - Self { - path, +impl<'a, C: AsRef, R: CommandRunner> WordpressTranslation<'a, C, PathBuf, R> { + pub fn new>(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self { + WordpressTranslation { + path: [path.as_ref(), "wp-content/languages".as_ref()] + .iter() + .collect(), version, locale, command_runner, @@ -28,7 +33,7 @@ impl<'a, D, C: AsRef, R: CommandRunner> Translation<'a, C, D, R> { } } -impl, D: AsRef, R: CommandRunner> Translation<'_, C, D, R> { +impl, D: AsRef, R: CommandRunner> WordpressTranslation<'_, C, D, R> { fn get_pairs(&self) -> Vec<(String, PathBuf)> { let version_x = self .version @@ -59,7 +64,7 @@ impl, D: AsRef, R: CommandRunner> Translation<'_, C, D, R> { } } -impl, D: AsRef, R: CommandRunner> Symbol for Translation<'_, C, D, R> { +impl, D: AsRef, R: CommandRunner> Symbol for WordpressTranslation<'_, C, D, R> { fn target_reached(&self) -> Result> { let mut newest = String::new(); let match_date = Regex::new("(?m)^\"PO-Revision-Date: (.+)\\+0000\\\\n\"$").unwrap(); @@ -110,4 +115,27 @@ impl, D: AsRef, R: CommandRunner> Symbol for Translation<'_, } Ok(()) } + + fn get_prerequisites(&self) -> Vec { + vec![Resource::new("dir", self.path.as_ref().to_str().unwrap())] + } + + fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { + Box::new(SymbolAction::new(runner, self)) + } + + fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box + where + Self: 'b, + { + Box::new(OwnedSymbolAction::new(runner, *self)) + } +} + +impl, D: AsRef, 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()) + } } diff --git a/src/templates/mod.rs b/src/templates/mod.rs deleted file mode 100644 index 5115a02..0000000 --- a/src/templates/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod nginx; -pub mod php; -pub mod systemd; diff --git a/src/templates/nginx/mod.rs b/src/templates/nginx/mod.rs deleted file mode 100644 index 1134378..0000000 --- a/src/templates/nginx/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod server; -pub use server::*; - -use std::path::Path; -pub fn acme_challenges_snippet>(path: P) -> String { - format!( - "location ^~ /.well-known/acme-challenge/ {{ - alias {}/; - try_files $uri =404; -}}", - path.as_ref().to_str().unwrap() - ) -} diff --git a/src/templates/nginx/server.rs b/src/templates/nginx/server.rs deleted file mode 100644 index 51178c4..0000000 --- a/src/templates/nginx/server.rs +++ /dev/null @@ -1,202 +0,0 @@ -use std::fmt::Display; -use std::path::Path; - -pub fn default_server>(challenges_snippet_path: P) -> String { - format!( - "server {{ - listen 80 default_server; - listen [::]:80 default_server; - include \"{}\"; - }}", - challenges_snippet_path.as_ref().to_str().unwrap() - ) -} - -pub fn server_config, K: AsRef, T: Display, S: AsRef>( - domain: D, - cert_path: C, - key_path: K, - content: T, - challenges_snippet_path: S, -) -> String { - format!( - "server {{ - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name {}; - include \"{}\"; - - ssl_certificate {}; - ssl_certificate_key {}; - add_header Strict-Transport-Security \"max-age=31536000\"; - - {} -}} - -# Redirect all HTTP links to the matching HTTPS page -server {{ - listen 80; - listen [::]:80; - server_name {0}; - include \"{1}\"; - - location / {{ - return 301 https://$host$request_uri; - }} -}} -", - domain, - challenges_snippet_path.as_ref().to_str().unwrap(), - cert_path.as_ref().to_str().unwrap(), - key_path.as_ref().to_str().unwrap(), - content - ) -} - -pub fn php_snippet, STATIC: AsRef>( - index: &'static str, - socket_path: SOCKET, - static_path: STATIC, -) -> String { - format!( - "root {}; - index {}; - location ~ [^/]\\.php(/|$) {{ - fastcgi_pass unix:{}; - include \"snippets/fastcgi-php.conf\"; - }}", - static_path.as_ref().to_str().unwrap(), - index, - socket_path.as_ref().to_str().unwrap() - ) -} - -pub fn redir_snippet(target: &str) -> String { - format!( - "location / {{ - return 301 $scheme://{}$request_uri; - }}", - target - ) -} - -pub trait SocketSpec { - fn to_nginx(&self) -> String; -} - -impl> SocketSpec for T { - 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) - } -} - -pub fn proxy_snippet>( - socket_path: &S, - static_path: STATIC, -) -> String { - format!( - "root {}; - location / {{ - try_files $uri @proxy; - }} - - location @proxy {{ - include fastcgi_params; - proxy_pass http://{}; - proxy_redirect off; - }}", - static_path.as_ref().to_str().unwrap(), - socket_path.to_nginx() - ) -} - -pub fn static_snippet>(static_path: S) -> String { - format!( - "root {}; - try_files $uri $uri/ $uri.html =404; - ", - static_path.as_ref().to_str().unwrap() - ) -} - -pub fn dokuwiki_snippet() -> String { - " - 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; - }".into() -} - -pub fn nextcloud_snippet() -> String { - " - 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; - } - " - .into() -} diff --git a/src/templates/php.rs b/src/templates/php.rs deleted file mode 100644 index 87fc6bf..0000000 --- a/src/templates/php.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::path::Path; - -pub fn fpm_pool_config, S: AsRef>( - user_name: U, - socket_path: S, - max_children: usize, -) -> String { - 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.as_ref(), - socket_path.as_ref().to_str().unwrap(), - max_children, - ) -} diff --git a/src/templates/systemd.rs b/src/templates/systemd.rs deleted file mode 100644 index 2369489..0000000 --- a/src/templates/systemd.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::path::Path; - -pub fn socket_service( - socket_path: impl AsRef, - exec: &str, - dir: impl AsRef, - additional: &str, -) -> String { - format!( - "[Service] -ExecStartPre=/bin/rm -f {} -ExecStart={} -WorkingDirectory={} -Restart=always -{} - -[Install] -WantedBy=default.target - ", - socket_path.as_ref().to_str().unwrap(), - exec, - dir.as_ref().to_str().unwrap(), - additional - ) -} - -pub fn nodejs_service, S: AsRef>( - nodejs_path: N, - socket_path: S, - dir: impl AsRef, -) -> String { - socket_service( - &socket_path, - &format!("/usr/bin/nodejs {}", nodejs_path.as_ref().to_str().unwrap()), - dir, - &format!( - "Environment=NODE_ENV=production -Environment=PORT={0} -ExecStartPost=/bin/sh -c 'sleep 1 && chmod 666 {0}' -#RuntimeDirectory=service -#RuntimeDirectoryMode=766", - socket_path.as_ref().to_str().unwrap() - ), - ) -} diff --git a/src/to_artifact.rs b/src/to_artifact.rs deleted file mode 100644 index 2d20807..0000000 --- a/src/to_artifact.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::resources::Resource; - -pub trait ToArtifact { - type Artifact; -} - -macro_rules! to_artifact { - ( $($name:ident)* ) => ( - #[allow(non_snake_case)] - impl<$($name: Resource,)*> ToArtifact for ($($name,)*) - { - type Artifact = ($($name::Artifact,)*); - } - ); -} - -for_each_tuple!(to_artifact); - -impl ToArtifact for Option { - type Artifact = Option; -} - -impl ToArtifact for T { - type Artifact = T::Artifact; -} diff --git a/static_files/lets_encrypt_x3_cross_signed.pem b/static_files/lets_encrypt_x3_cross_signed.pem deleted file mode 100644 index 0002462..0000000 --- a/static_files/lets_encrypt_x3_cross_signed.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow -SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT -GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF -q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 -SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 -Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA -a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj -/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T -AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG -CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv -bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k -c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw -VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC -ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz -MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu -Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF -AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo -uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ -wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu -X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG -PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 -KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== ------END CERTIFICATE----- diff --git a/tests/file.rs b/tests/file.rs index 92210c5..d11172c 100644 --- a/tests/file.rs +++ b/tests/file.rs @@ -2,7 +2,7 @@ use schematics::symbols::file::File as FileSymbol; use schematics::symbols::Symbol; use std::fs::File; use std::io::Write; -use std::path::{Path, PathBuf}; +use std::path::Path; use tempdir::TempDir; fn get_dir(content: Option<&str>) -> TempDir { @@ -18,8 +18,11 @@ fn get_dir(content: Option<&str>) -> TempDir { tmp_dir } -fn get_symbol(path: &Path) -> FileSymbol { - FileSymbol::new(path.join("filename"), "target content") +fn get_symbol(path: &Path) -> FileSymbol<&str, String> { + FileSymbol::new( + String::from(path.join("filename").to_str().unwrap()), + "target content", + ) } // Normal cases @@ -78,7 +81,7 @@ fn no_file() { #[test] fn may_not_read_file() { - let symbol = get_symbol(&Path::new("/etc/shadow")); + let symbol = get_symbol(&Path::new("/etc/passwd")); assert_eq!(symbol.target_reached().is_err(), true); } diff --git a/tests/setup.rs b/tests/setup.rs deleted file mode 100644 index b8aadf7..0000000 --- a/tests/setup.rs +++ /dev/null @@ -1,72 +0,0 @@ -use schematics::resources::{Cert, Csr, GitCheckout}; -use schematics::schema::SymbolRunner; -use schematics::symbols::Symbol; -use schematics::Setup; -use std::cell::RefCell; -use std::error::Error; -use std::fmt::Debug; -use std::rc::Rc; - -struct TestSymbolRunner { - count: Rc>, -} - -impl SymbolRunner for TestSymbolRunner { - fn run_symbol( - &self, - _symbol: &S, - _force: bool, - ) -> Result> { - *self.count.borrow_mut() += 1; - Ok(false) - } -} - -#[test] -fn runs_only_once() { - let count = Rc::new(RefCell::new(0)); - let runner = TestSymbolRunner { - count: Rc::clone(&count), - }; - let mut setup = Setup::new(runner); - assert_eq!( - (setup.add(Csr("somehost")).unwrap().0).0.to_str().unwrap(), - "/etc/ssl/local_certs/somehost.csr", - ); - assert_eq!( - (setup.add(Csr("somehost")).unwrap().0).0.to_str().unwrap(), - "/etc/ssl/local_certs/somehost.csr", - ); - assert_eq!(*count.borrow(), 2 + 5); // Key and CSR + 5 dirs -} - -#[test] -fn can_create_an_acme_cert() { - let count = Rc::new(RefCell::new(0)); - let runner = TestSymbolRunner { - count: Rc::clone(&count), - }; - let mut setup = Setup::new(runner); - assert_eq!( - (setup.add(Cert("somehost")).unwrap().0).0.to_str().unwrap(), - "/etc/ssl/local_certs/somehost.crt", - ); - assert_eq!(*count.borrow(), 15); -} - -#[test] -fn can_create_a_git_checkout() { - let count = Rc::new(RefCell::new(0)); - let runner = TestSymbolRunner { - count: Rc::clone(&count), - }; - let mut setup = Setup::new(runner); - setup - .add(GitCheckout( - "/tmp/somepath".into(), - "/tmp/some_src_repo", - "master", - )) - .unwrap(); - assert_eq!(*count.borrow(), 3); -} diff --git a/tests/storage.rs b/tests/storage.rs index cb4bc61..964c3c5 100644 --- a/tests/storage.rs +++ b/tests/storage.rs @@ -16,7 +16,7 @@ fn get_dir<'a, I: IntoIterator>(content: I) -> TempDir { } fn get_storage(path: &Path) -> SimpleStorage { - SimpleStorage::new(path.join("_filename")) + SimpleStorage::new(path.to_str().unwrap().into(), "filename".into()) } // Normal cases @@ -29,7 +29,7 @@ fn single_file() { assert!( Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())) .unwrap() - .is_match(storage.write_filename().to_str().unwrap()) + .is_match(&storage.write_filename()) ); assert_eq!( dir.path().join("_filename").join("12345"), @@ -46,7 +46,7 @@ fn two_files() { assert!( Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())) .unwrap() - .is_match(storage.write_filename().to_str().unwrap()) + .is_match(&storage.write_filename()) ); assert_eq!( dir.path().join("_filename").join("23456"), @@ -63,7 +63,7 @@ fn another_two_files() { assert!( Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())) .unwrap() - .is_match(storage.write_filename().to_str().unwrap()) + .is_match(&storage.write_filename()) ); assert_eq!( dir.path().join("_filename").join("23456"), @@ -80,7 +80,7 @@ fn three_files() { assert!( Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())) .unwrap() - .is_match(storage.write_filename().to_str().unwrap()) + .is_match(&storage.write_filename()) ); assert_eq!( dir.path().join("_filename").join("23456"), @@ -99,7 +99,7 @@ fn empty_storage() { assert!( Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())) .unwrap() - .is_match(storage.write_filename().to_str().unwrap()) + .is_match(&storage.write_filename()) ); assert!(storage.read_filename().is_err()); assert_eq!( @@ -121,7 +121,7 @@ fn empty_storage_for_filename() { assert!( Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())) .unwrap() - .is_match(storage.write_filename().to_str().unwrap()) + .is_match(&storage.write_filename()) ); assert!(storage.read_filename().is_err()); assert_eq!( @@ -143,7 +143,7 @@ fn bad_file() { assert!( Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())) .unwrap() - .is_match(storage.write_filename().to_str().unwrap()) + .is_match(&storage.write_filename()) ); assert!(storage.read_filename().is_err()); assert_eq!(