From 907a4962c5843b51cf19851cfae7cb04aa53850d Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Thu, 26 Dec 2019 20:50:23 +0100 Subject: [PATCH] New architecture --- Cargo.toml | 1 + src/artifacts/mod.rs | 18 + src/build.rs | 5 + src/builder.rs | 767 ++++++++++++++++++ src/factory.rs | 125 --- src/lib.rs | 16 +- src/locator.rs | 443 ++++++++++ src/repository.rs | 36 - src/resources/mod.rs | 289 ++++++- src/schema.rs | 200 ++--- src/setup.rs | 345 ++++++++ src/static_files.rs | 1 + src/storage.rs | 23 +- src/symbols/acme/account_key.rs | 85 -- src/symbols/acme/cert.rs | 101 +-- src/symbols/acme/chain.rs | 117 --- src/symbols/acme/mod.rs | 113 +-- src/symbols/concat.rs | 36 +- src/symbols/cron.rs | 31 +- src/symbols/dir.rs | 48 +- src/symbols/factory.rs | 436 ---------- src/symbols/file.rs | 39 +- src/symbols/git/checkout.rs | 103 +-- src/symbols/git/mod.rs | 6 +- src/symbols/hook.rs | 196 ----- src/symbols/list.rs | 213 ----- src/symbols/mariadb/database.rs | 38 +- src/symbols/mariadb/database_dump.rs | 75 -- src/symbols/mariadb/dump.rs | 56 ++ src/symbols/mariadb/mod.rs | 8 +- src/symbols/mariadb/user.rs | 37 +- src/symbols/mod.rs | 70 +- src/symbols/nginx/mod.rs | 1 - src/symbols/nginx/server.rs | 246 ------ src/symbols/noop.rs | 31 - src/symbols/npm.rs | 27 +- src/symbols/owner.rs | 64 +- src/symbols/postgresql/database.rs | 16 +- ...stored_directory.rs => saved_directory.rs} | 87 +- src/symbols/systemd/mod.rs | 10 +- src/symbols/systemd/reload.rs | 42 +- src/symbols/systemd/user_service.rs | 165 +--- src/symbols/systemd/user_session.rs | 62 +- src/symbols/tls/csr.rs | 96 +-- src/symbols/tls/key.rs | 76 +- src/symbols/tls/mod.rs | 6 +- src/symbols/tls/self_signed_cert.rs | 119 --- src/symbols/user.rs | 187 +---- src/symbols/wordpress/mod.rs | 7 +- src/symbols/wordpress/plugin.rs | 39 +- src/symbols/wordpress/translation.rs | 48 +- src/templates/mod.rs | 3 + src/templates/nginx/mod.rs | 13 + src/templates/nginx/server.rs | 202 +++++ src/templates/php.rs | 24 + src/templates/systemd.rs | 45 + src/to_artifact.rs | 25 + static_files/lets_encrypt_x3_cross_signed.pem | 27 + tests/file.rs | 11 +- tests/setup.rs | 72 ++ tests/storage.rs | 16 +- 61 files changed, 2743 insertions(+), 3101 deletions(-) create mode 100644 src/artifacts/mod.rs create mode 100644 src/builder.rs delete mode 100644 src/factory.rs create mode 100644 src/locator.rs delete mode 100644 src/repository.rs create mode 100644 src/setup.rs create mode 100644 src/static_files.rs delete mode 100644 src/symbols/acme/account_key.rs delete mode 100644 src/symbols/acme/chain.rs delete mode 100644 src/symbols/factory.rs delete mode 100644 src/symbols/hook.rs delete mode 100644 src/symbols/list.rs delete mode 100644 src/symbols/mariadb/database_dump.rs create mode 100644 src/symbols/mariadb/dump.rs delete mode 100644 src/symbols/nginx/mod.rs delete mode 100644 src/symbols/nginx/server.rs delete mode 100644 src/symbols/noop.rs rename src/symbols/{stored_directory.rs => saved_directory.rs} (52%) delete mode 100644 src/symbols/tls/self_signed_cert.rs create mode 100644 src/templates/mod.rs create mode 100644 src/templates/nginx/mod.rs create mode 100644 src/templates/nginx/server.rs create mode 100644 src/templates/php.rs create mode 100644 src/templates/systemd.rs create mode 100644 src/to_artifact.rs create mode 100644 static_files/lets_encrypt_x3_cross_signed.pem create mode 100644 tests/setup.rs diff --git a/Cargo.toml b/Cargo.toml index 94b28bd..8000d82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ 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 new file mode 100644 index 0000000..c02538b --- /dev/null +++ b/src/artifacts/mod.rs @@ -0,0 +1,18 @@ +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 492cfad..b6dfdd3 100644 --- a/src/build.rs +++ b/src/build.rs @@ -40,3 +40,8 @@ pub fn create_static_output_files(source_dir: &str) { .unwrap(); } } + +#[allow(unused)] +fn main() { + create_static_output_files("static_files"); +} diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..f367c04 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,767 @@ +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 deleted file mode 100644 index e529c44..0000000 --- a/src/factory.rs +++ /dev/null @@ -1,125 +0,0 @@ -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 57ed75b..7a09805 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![deny( +#![warn( macro_use_extern_crate, meta_variable_misuse, non_ascii_idents, @@ -32,10 +32,20 @@ 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 new file mode 100644 index 0000000..4340cdb --- /dev/null +++ b/src/locator.rs @@ -0,0 +1,443 @@ +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 deleted file mode 100644 index 5634b41..0000000 --- a/src/repository.rs +++ /dev/null @@ -1,36 +0,0 @@ -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 f2f1363..4e717af 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -1,15 +1,282 @@ -#[derive(PartialEq, Eq, Hash, Clone)] -pub struct Resource(pub String, pub String); +use crate::artifacts::{ + DatabaseName as DatabaseNameArtifact, Path as PathArtifact, ServiceName as ServiceNameArtifact, + UserName as UserNameArtifact, +}; +use std::hash::Hash; +use std::path::PathBuf; -impl<'a> Resource { - pub fn new, B: Into>(rtype: A, value: B) -> Self { - Self(rtype.into(), value.into()) - } +pub trait Resource { + type Artifact; +} - pub fn get_type(&self) -> &str { - &self.0 - } - pub fn get_value(&self) -> &str { - &self.1 +#[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 + } + } + })* } } + +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 9fe811c..8b1cc94 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,10 +1,19 @@ +use crate::loggers::Logger; +use crate::symbols::Symbol; use std::cell::RefCell; use std::error::Error; use std::fmt; +use std::fmt::Debug; -use crate::loggers::Logger; -use crate::repository::SymbolRepository; -use crate::symbols::{Symbol, SymbolRunner}; +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) + } +} #[derive(Debug)] pub enum SymbolRunError { @@ -46,31 +55,47 @@ impl InitializingSymbolRunner { logger: RefCell::new(logger), } } -} -impl SymbolRunner for InitializingSymbolRunner { - fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { + 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 { - logger.write(format!("{} already reached", symbol).as_str()); + Ok(()) + } else { + Err(Box::new(SymbolRunError::ExecuteDidNotReach(()))) + } + } +} + +impl SymbolRunner for InitializingSymbolRunner { + fn run_symbol(&self, symbol: &S, force: bool) -> Result> { + let mut logger = self.logger.borrow_mut(); + let executed = if force { + logger.debug("Forcing symbol execution"); + drop(logger); + self.exec_symbol(symbol)?; + true } 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()?; - logger.debug( - format!( - "Symbol reports target_reached: {:?} (should be true)", - target_reached - ) - .as_str(), - ); - if !target_reached { - return Err(Box::new(SymbolRunError::ExecuteDidNotReach(()))); + 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)?; } - } - Ok(()) + !target_reached + }; + Ok(executed) } } @@ -87,24 +112,26 @@ impl DrySymbolRunner { } impl SymbolRunner for DrySymbolRunner { - fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { + fn run_symbol(&self, symbol: &S, force: bool) -> Result> { let mut logger = self.logger.borrow_mut(); - 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(()) + 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) } } -pub struct ReportingSymbolRunner<'a, R: SymbolRunner, L: Logger>(&'a R, RefCell); +pub struct ReportingSymbolRunner<'a, R, L>(&'a R, RefCell); -impl<'a, R, L> ReportingSymbolRunner<'a, R, L> -where - R: SymbolRunner, - L: Logger, -{ +impl<'a, R, L> ReportingSymbolRunner<'a, R, L> { pub fn new(symbol_runner: &'a R, logger: L) -> Self { ReportingSymbolRunner(symbol_runner, RefCell::new(logger)) } @@ -115,92 +142,20 @@ where R: SymbolRunner, L: Logger, { - fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { + fn run_symbol(&self, symbol: &S, force: bool) -> Result> { let mut logger = self.1.borrow_mut(); - logger.debug(format!("Running symbol {}", symbol).as_str()); - let res = self.0.run_symbol(symbol); + logger.debug(format!("Running symbol {:?}", symbol).as_str()); + let res = self.0.run_symbol(symbol, force); 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; @@ -210,7 +165,7 @@ mod test { use crate::loggers::Logger; use crate::schema::InitializingSymbolRunner; use crate::schema::SymbolRunner; - use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction}; + use crate::symbols::Symbol; #[derive(Debug, PartialEq, Clone)] enum DummySymbolError { @@ -229,6 +184,7 @@ mod test { } } + #[derive(Debug)] struct DummySymbol<'a> { _execute: &'a dyn Fn() -> Result<(), Box>, _target_reached: &'a dyn Fn() -> Result>, @@ -241,23 +197,6 @@ 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> { @@ -336,3 +275,4 @@ mod test { assert_eq!(err.description(), "Description"); } } +*/ diff --git a/src/setup.rs b/src/setup.rs new file mode 100644 index 0000000..ebf4e71 --- /dev/null +++ b/src/setup.rs @@ -0,0 +1,345 @@ +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 new file mode 100644 index 0000000..c2d580d --- /dev/null +++ b/src/static_files.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/static_files.rs")); diff --git a/src/storage.rs b/src/storage.rs index ea22765..a963414 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,32 +1,33 @@ 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) -> String; - fn read_filename(&self) -> Result>; + fn write_filename(&self) -> PathBuf; + fn read_filename(&self) -> Result>; fn recent_date(&self) -> Result>; } -#[derive(Clone)] -pub struct SimpleStorage(String, String); +#[derive(Debug, Clone)] +pub struct SimpleStorage(PathBuf); impl SimpleStorage { - pub const fn new(base: String, filename: String) -> Self { - Self(base, filename) + pub const fn new(base: PathBuf) -> Self { + Self(base) } - fn get_path(&self, date: Option) -> String { + fn get_path(&self, date: Option) -> PathBuf { match date { - Some(d) => format!("{}/_{}/{}", self.0, self.1, d), - None => format!("{}/_{}", self.0, self.1), + Some(d) => self.0.join(d.to_string()), + None => self.0.clone(), } } } impl Storage for SimpleStorage { - fn write_filename(&self) -> String { + fn write_filename(&self) -> PathBuf { self.get_path(Some( SystemTime::now() .duration_since(UNIX_EPOCH) @@ -35,7 +36,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 deleted file mode 100644 index e2807a8..0000000 --- a/src/symbols/acme/account_key.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::error::Error; -use std::fmt; -use std::path::Path; - -use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct AcmeAccountKey<'a, P: AsRef, 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 2af81b4..9145301 100644 --- a/src/symbols/acme/cert.rs +++ b/src/symbols/acme/cert.rs @@ -1,80 +1,61 @@ +use crate::command_runner::CommandRunner; +use crate::symbols::Symbol; +use std::borrow::Borrow; use std::error::Error; -use std::fmt; use std::fs::File as FsFile; use std::io::Write; -use std::path::{Path, PathBuf}; - -use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; +use std::marker::PhantomData; +use std::path::Path; -pub struct AcmeCert< - 'a, - D: AsRef, - R: AsRef, - C: CommandRunner, - K: AsRef, - CH: AsRef, -> { +#[derive(Debug)] +pub struct Cert<_C, C, D, P> { domain: D, - command_runner: &'a C, - root_cert_path: R, - account_key_path: K, - challenges_path: CH, + command_runner: C, + root_cert_path: P, + account_key_path: P, + challenges_path: P, + csr_path: P, + cert_path: P, + phantom: PhantomData<_C>, } -impl<'a, D: AsRef, R: AsRef, C: CommandRunner, K: AsRef, CH: AsRef> - AcmeCert<'a, D, R, C, K, CH> -{ +impl<_C, C, D, P> Cert<_C, C, D, P> { pub fn new( domain: D, - command_runner: &'a C, - root_cert_path: R, - account_key_path: K, - challenges_path: CH, + command_runner: C, + root_cert_path: P, + account_key_path: P, + challenges_path: P, + csr_path: P, + cert_path: P, ) -> Self { - AcmeCert { + Self { domain, command_runner, root_cert_path, account_key_path, challenges_path, + csr_path, + cert_path, + phantom: PhantomData::default(), } } - - fn get_csr_path(&self) -> PathBuf { - format!("/etc/ssl/local_certs/{}.csr", self.domain.as_ref()).into() - } - - fn get_cert_path(&self) -> PathBuf { - format!("/etc/ssl/local_certs/{}.crt", self.domain.as_ref()).into() - } -} - -impl, 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, R: AsRef, C: CommandRunner, K: AsRef, CH: AsRef> Symbol - for AcmeCert<'_, D, R, C, K, CH> -{ +impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef, P: AsRef> Symbol for Cert<_C, C, D, P> { fn target_reached(&self) -> Result> { - if !self.get_cert_path().exists() { + if !self.cert_path.as_ref().exists() { return Ok(false); } - let output = self.command_runner.run_with_args( + let output = self.command_runner.borrow().run_with_args( "openssl", args![ "x509", "-in", - self.get_cert_path(), + self.cert_path.as_ref(), "-noout", "-subject", "-checkend", @@ -92,13 +73,14 @@ impl, R: AsRef, C: CommandRunner, K: AsRef, CH: AsRef< Ok( self .command_runner + .borrow() .run_successfully( "openssl", args![ "verify", "--untrusted", self.root_cert_path.as_ref(), - self.get_cert_path(), + self.cert_path.as_ref(), ], ) .is_ok(), @@ -118,36 +100,21 @@ impl, R: AsRef, C: CommandRunner, K: AsRef, CH: AsRef< } fn execute(&self) -> Result<(), Box> { - let output = self.command_runner.get_output( + let output = self.command_runner.borrow().get_output( "acme-tiny", args![ "--account-key", self.account_key_path.as_ref(), "--csr", - self.get_csr_path(), + self.csr_path.as_ref(), "--acme-dir", self.challenges_path.as_ref(), ], )?; - let mut file = FsFile::create(self.get_cert_path())?; + let mut file = FsFile::create(self.cert_path.as_ref())?; file.write_all(&output)?; Ok(()) } - - fn get_prerequisites(&self) -> Vec { - 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 deleted file mode 100644 index d62eb9b..0000000 --- a/src/symbols/acme/chain.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::error::Error; -use std::fmt; -use std::fs::File as FsFile; -use std::io::Write; -use std::path::{Path, PathBuf}; - -use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct AcmeCertChain<'a, D: AsRef, 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 c6390ef..adf8941 100644 --- a/src/symbols/acme/mod.rs +++ b/src/symbols/acme/mod.rs @@ -1,113 +1,2 @@ -use std::borrow::Cow; -use std::path::{Path, PathBuf}; - -use crate::command_runner::CommandRunner; -use crate::command_runner::SetuidCommandRunner; -use crate::symbols::concat::Concat; -use crate::symbols::dir::Dir; -use crate::symbols::file::File; -use crate::symbols::list::List; -use crate::symbols::owner::Owner; -use crate::symbols::Symbol; - -mod account_key; mod cert; -mod chain; - -pub use self::account_key::AcmeAccountKey; -pub use self::cert::AcmeCert; -pub use self::chain::AcmeCertChain; - -const ROOT_CERT_FILE_NAME: &str = "lets_encrypt_x3_cross_signed.pem"; -const ACCOUNT_KEY_FILE_NAME: &str = "account.key"; - -pub struct Factory<'a, U: AsRef, 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()), - ), - )) - } -} +pub use self::cert::Cert; diff --git a/src/symbols/concat.rs b/src/symbols/concat.rs index 186dbcc..cdde300 100644 --- a/src/symbols/concat.rs +++ b/src/symbols/concat.rs @@ -1,13 +1,11 @@ +use crate::symbols::Symbol; use std::error::Error; -use std::fmt; use std::fs::{metadata, File}; use std::io::copy; use std::marker::PhantomData; use std::path::Path; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - +#[derive(Debug)] pub struct Concat { target: D, sources: S, @@ -46,34 +44,4 @@ 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 216b7cc..e30978d 100644 --- a/src/symbols/cron.rs +++ b/src/symbols/cron.rs @@ -1,19 +1,17 @@ -use std::error::Error; -use std::fmt; - use crate::command_runner::CommandRunner; +use crate::symbols::Symbol; +use std::error::Error; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct Cron<'r, C: AsRef, U: AsRef, R: CommandRunner> { +#[derive(Debug)] +pub struct Cron<'r, C, U, R> { user: U, content: C, command_runner: &'r R, } -impl<'r, U: AsRef, R: CommandRunner> Cron<'r, String, U, R> { +impl<'r, U, R> Cron<'r, String, U, R> { pub fn new>(user: U, content: C, command_runner: &'r R) -> Self { - Cron { + Self { user, content: String::from(content.as_ref()) + "\n", command_runner, @@ -40,21 +38,4 @@ impl, 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 34d3757..c97c12a 100644 --- a/src/symbols/dir.rs +++ b/src/symbols/dir.rs @@ -1,23 +1,21 @@ +use crate::symbols::Symbol; use std::error::Error; -use std::fmt; use std::fs; use std::io; use std::path::Path; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct Dir> { - path: D, +#[derive(Debug)] +pub struct Dir

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

Dir

{ + pub fn new(path: P) -> 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); @@ -35,36 +33,4 @@ 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 deleted file mode 100644 index 6331ff7..0000000 --- a/src/symbols/factory.rs +++ /dev/null @@ -1,436 +0,0 @@ -use std::borrow::Cow; -use std::path::Path; -use std::path::PathBuf; - -use crate::command_runner::CommandRunner; -use crate::storage::{SimpleStorage, Storage}; -use crate::symbols::acme::Factory as AcmeFactory; -use crate::symbols::cron::Cron; -use crate::symbols::file::File; -use crate::symbols::git::checkout::GitCheckout; -use crate::symbols::hook::Hook; -use crate::symbols::list::List; -use crate::symbols::mariadb::{DatabaseDump, MariaDBDatabase, MariaDBUser}; -use crate::symbols::nginx::server::{server_config, NginxServer}; -use crate::symbols::owner::Owner; -use crate::symbols::stored_directory::{StorageDirection, StoredDirectory}; -use crate::symbols::systemd::reload::ReloadService; -use crate::symbols::systemd::user_service::UserService; -use crate::symbols::tls::SelfSignedTlsCert; -use crate::symbols::Symbol; - -pub trait Policy { - fn user_name_for_host(&self, host_name: &'static str) -> String { - host_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part) - } - fn home_for_user(&self, user_name: &str) -> PathBuf { - format!("/home/{}", user_name).into() - } - fn socket_path(&self, user_name: &str, service_name: &str) -> PathBuf { - format!("/var/tmp/{}-{}.socket", user_name, service_name).into() - } - fn acme_user(&self) -> Cow<'_, str> { - "acme".into() - } - fn php_version(&self) -> &'static str { - "7.0" - } -} - -pub struct DefaultPolicy; - -impl Policy for DefaultPolicy {} - -pub struct SymbolFactory<'a, C: CommandRunner, P: Policy> { - command_runner: &'a C, - acme_factory: AcmeFactory<'a, Cow<'a, str>, PathBuf, &'a str, C>, - policy: &'a P, -} - -impl<'b, C: 'b + CommandRunner, P: 'b + Policy> SymbolFactory<'b, C, P> { - pub fn new(command_runner: &'b C, policy: &'b P, cert: &'b str) -> Self { - let acme_user = policy.acme_user(); - let acme_home = policy.home_for_user(&acme_user); - let acme_factory = AcmeFactory::new(acme_user, acme_home, cert, command_runner); - SymbolFactory { - command_runner, - acme_factory, - policy, - } - } - - pub fn get_cert<'a, H: 'a + AsRef + 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 f6df2c0..25fe3fa 100644 --- a/src/symbols/file.rs +++ b/src/symbols/file.rs @@ -1,26 +1,22 @@ +use crate::symbols::Symbol; use std::error::Error; -use std::fmt; use std::fs::File as FsFile; - use std::io::{Read, Write}; - use std::path::Path; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct File, D: AsRef> { +#[derive(Debug)] +pub struct File { path: D, content: C, } -impl, D: AsRef> File { +impl File { pub fn new(path: D, content: C) -> Self { Self { path, content } } } -impl, D: AsRef> Symbol for File { +impl, C: AsRef> Symbol for File { fn target_reached(&self) -> Result> { if !self.path.as_ref().exists() { return Ok(false); @@ -44,29 +40,4 @@ impl, D: 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 b837061..09a5a5a 100644 --- a/src/symbols/git/checkout.rs +++ b/src/symbols/git/checkout.rs @@ -1,62 +1,48 @@ +use crate::command_runner::CommandRunner; +use crate::symbols::Symbol; +use std::borrow::Borrow; use std::error::Error; use std::ffi::OsStr; -use std::fmt; -use std::fs::metadata; -use std::io; +use std::marker::PhantomData; use std::path::Path; -use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct GitCheckout<'a, C: CommandRunner, T: AsRef> { - target: T, - source: &'a str, - branch: &'a str, - command_runner: &'a C, +#[derive(Debug)] +pub struct Checkout<_C, C, P, S, B> { + target: P, + source: S, + branch: B, + command_runner: C, + phantom: PhantomData<_C>, } -impl<'a, C: CommandRunner, T: AsRef> GitCheckout<'a, C, T> { - pub fn new(target: T, source: &'a str, branch: &'a str, command_runner: &'a C) -> Self { - GitCheckout { +impl Checkout<_C, C, P, S, B> { + pub fn new(target: P, source: S, branch: B, command_runner: C) -> Self { + Self { target, source, branch, command_runner, + phantom: PhantomData::default(), } } } -impl> 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> { +impl, P: AsRef, S, B> Checkout { + fn _run_in_target_repo(&self, args: &[impl AsRef]) -> 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.get_output("git", &new_args) + self.command_runner.borrow().get_output("git", &new_args) } } -impl> Symbol for GitCheckout<'_, C, T> { +impl, P: AsRef, S: AsRef, B: AsRef> Symbol + for Checkout +{ fn target_reached(&self) -> Result> { - if let Err(e) = metadata(self.target.as_ref()) { - return if e.kind() == io::ErrorKind::NotFound { - Ok(false) - } else { - Err(Box::new(e)) - }; + if !self.target.as_ref().exists() { + return Ok(false); } - self._run_in_target_repo(&["fetch", self.source, self.branch])?; + self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?; // git rev-list resolves tag objects let fetch_head = self._run_in_target_repo(&["rev-list", "-1", "FETCH_HEAD"])?; let head = self._run_in_target_repo(&["rev-list", "-1", "HEAD"])?; @@ -65,48 +51,23 @@ impl> Symbol for GitCheckout<'_, C, T> { fn execute(&self) -> Result<(), Box> { if !self.target.as_ref().exists() { - return self.command_runner.run_successfully( + return self.command_runner.borrow().run_successfully( "git", - &[ - OsStr::new("clone"), - "--depth".as_ref(), - "1".as_ref(), - "-b".as_ref(), + args![ + "clone", + "--depth", + "1", + "-b", self.branch.as_ref(), self.source.as_ref(), - self.target.as_ref().as_ref(), + self.target.as_ref(), ], ); } - self._run_in_target_repo(&["fetch", self.source, self.branch])?; + self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?; self._run_in_target_repo(&["merge", "FETCH_HEAD"])?; Ok(()) } - - fn get_prerequisites(&self) -> Vec { - 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 d3e2df1..580afe8 100644 --- a/src/symbols/git/mod.rs +++ b/src/symbols/git/mod.rs @@ -1,2 +1,4 @@ -pub mod checkout; -pub mod submodules; +mod checkout; +//pub mod submodules; + +pub use checkout::Checkout; diff --git a/src/symbols/hook.rs b/src/symbols/hook.rs deleted file mode 100644 index 3cd4519..0000000 --- a/src/symbols/hook.rs +++ /dev/null @@ -1,196 +0,0 @@ -use std::error::Error; -use std::fmt; - -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct Hook -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 deleted file mode 100644 index 2df3247..0000000 --- a/src/symbols/list.rs +++ /dev/null @@ -1,213 +0,0 @@ -use std::error::Error; -use std::fmt; - -use crate::resources::Resource; -use crate::symbols::{Action, Symbol, SymbolRunner}; - -pub struct List<'a> { - symbols: Vec>, -} - -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 2d44165..4a35ae2 100644 --- a/src/symbols/mariadb/database.rs +++ b/src/symbols/mariadb/database.rs @@ -1,19 +1,18 @@ -use std::error::Error; -use std::fmt; -use std::path::Path; - use crate::command_runner::CommandRunner; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; +use crate::storage::Storage; +use crate::symbols::Symbol; +use std::error::Error; -pub struct MariaDBDatabase<'a, D: AsRef, S: AsRef, C: CommandRunner> { +#[derive(Debug)] +pub struct Database<'a, D, S, C> { db_name: D, seed_file: S, command_runner: &'a C, } -impl<'a, D: AsRef, S: AsRef, C: CommandRunner> MariaDBDatabase<'a, D, S, C> { +impl<'a, D, S, C: CommandRunner> Database<'a, D, S, C> { pub fn new(db_name: D, seed_file: S, command_runner: &'a C) -> Self { - MariaDBDatabase { + Self { db_name, seed_file, command_runner, @@ -28,15 +27,7 @@ impl<'a, D: AsRef, S: AsRef, C: CommandRunner> MariaDBDatabase<'a, D, } } -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> { +impl, S: Storage, C: CommandRunner> Symbol for Database<'_, D, S, C> { fn target_reached(&self) -> Result> { Ok( self @@ -55,22 +46,11 @@ impl, S: AsRef, C: CommandRunner> Symbol for MariaDBDatabase format!( "mariadb '{}' < {}", self.db_name.as_ref(), - self.seed_file.as_ref().to_str().unwrap() + self.seed_file.read_filename()?.to_str().unwrap() ), ], ) } - - fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { - 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 deleted file mode 100644 index 355ad1e..0000000 --- a/src/symbols/mariadb/database_dump.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::error::Error; -use std::fmt; -use std::str::FromStr; - -use crate::command_runner::CommandRunner; -use crate::storage::Storage; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct DatabaseDump<'a, N: AsRef, 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 new file mode 100644 index 0000000..e01d9d8 --- /dev/null +++ b/src/symbols/mariadb/dump.rs @@ -0,0 +1,56 @@ +use std::error::Error; +use std::str::FromStr; + +use crate::command_runner::CommandRunner; +use crate::storage::Storage; +use crate::symbols::Symbol; + +#[derive(Debug)] +pub struct Dump<'a, N, C, S> { + db_name: N, + storage: S, + command_runner: &'a C, +} + +impl<'a, N, C: CommandRunner, S> Dump<'a, N, C, S> { + pub fn new(db_name: N, storage: S, command_runner: &'a C) -> Self { + Self { + db_name, + storage, + command_runner, + } + } + + fn run_sql(&self, sql: &str) -> Result> { + 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 584a74c..46aec7d 100644 --- a/src/symbols/mariadb/mod.rs +++ b/src/symbols/mariadb/mod.rs @@ -1,7 +1,7 @@ mod database; -mod database_dump; +mod dump; mod user; -pub use self::database::MariaDBDatabase; -pub use self::database_dump::DatabaseDump; -pub use self::user::MariaDBUser; +pub use self::database::Database; +pub use self::dump::Dump; +pub use self::user::User; diff --git a/src/symbols/mariadb/user.rs b/src/symbols/mariadb/user.rs index 0037633..337f229 100644 --- a/src/symbols/mariadb/user.rs +++ b/src/symbols/mariadb/user.rs @@ -1,18 +1,16 @@ -use std::error::Error; -use std::fmt; - use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; +use crate::symbols::Symbol; +use std::error::Error; -pub struct MariaDBUser<'a, U: AsRef, C: CommandRunner> { +#[derive(Debug)] +pub struct User<'a, U, C> { user_name: U, command_runner: &'a C, } -impl<'a, U: AsRef, C: CommandRunner> MariaDBUser<'a, U, C> { +impl<'a, U: AsRef, C: CommandRunner> User<'a, U, C> { pub fn new(user_name: U, command_runner: &'a C) -> Self { - MariaDBUser { + Self { user_name, command_runner, } @@ -26,13 +24,7 @@ impl<'a, U: AsRef, C: CommandRunner> MariaDBUser<'a, 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> { +impl, C: CommandRunner> Symbol for User<'_, U, C> { fn target_reached(&self) -> Result> { Ok( self @@ -52,21 +44,6 @@ impl, C: CommandRunner> Symbol for MariaDBUser<'_, 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 779d186..a9932a8 100644 --- a/src/symbols/mod.rs +++ b/src/symbols/mod.rs @@ -1,88 +1,22 @@ -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: Display { +pub trait Symbol { 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 stored_directory; +pub mod saved_directory; pub mod systemd; pub mod tls; pub mod user; diff --git a/src/symbols/nginx/mod.rs b/src/symbols/nginx/mod.rs deleted file mode 100644 index 74f47ad..0000000 --- a/src/symbols/nginx/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod server; diff --git a/src/symbols/nginx/server.rs b/src/symbols/nginx/server.rs deleted file mode 100644 index dff4182..0000000 --- a/src/symbols/nginx/server.rs +++ /dev/null @@ -1,246 +0,0 @@ -use std::error::Error; -use std::fmt; -use std::io; -use std::path::{Path, PathBuf}; - -use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::file::File as FileSymbol; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -#[derive(Debug)] -pub enum NginxServerError { - 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 deleted file mode 100644 index 32943d5..0000000 --- a/src/symbols/noop.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::error::Error; -use std::fmt::{self, Display}; - -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct NoopSymbol; - -impl Symbol for NoopSymbol { - fn target_reached(&self) -> Result> { - 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 e9d79ed..afc42ba 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; -use crate::command_runner::CommandRunner; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct NpmInstall<'a, T: AsRef, C: CommandRunner> { +#[derive(Debug)] +pub struct Install<'a, T: AsRef, C: CommandRunner> { target: T, command_runner: &'a C, } -impl<'a, T: AsRef, C: CommandRunner> NpmInstall<'a, T, C> { +impl<'a, T: AsRef, C: CommandRunner> Install<'a, T, C> { pub fn new(target: T, command_runner: &'a C) -> Self { - NpmInstall { + Install { target, command_runner, } } } -impl, C: CommandRunner> fmt::Display for NpmInstall<'_, T, C> { +impl, C: CommandRunner> fmt::Display for Install<'_, T, C> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -29,7 +29,7 @@ impl, C: CommandRunner> fmt::Display for NpmInstall<'_, T, C> { } } -impl, C: CommandRunner> Symbol for NpmInstall<'_, T, C> { +impl, C: CommandRunner> Symbol for Install<'_, T, C> { fn target_reached(&self) -> Result> { if !self.target.as_ref().exists() { return Ok(false); @@ -61,17 +61,6 @@ impl, C: CommandRunner> Symbol for NpmInstall<'_, 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 eed6b92..a58c4fc 100644 --- a/src/symbols/owner.rs +++ b/src/symbols/owner.rs @@ -1,35 +1,37 @@ +use crate::command_runner::CommandRunner; +use crate::symbols::Symbol; +use std::borrow::Borrow; use std::error::Error; - -use std::fmt; use std::fs; +use std::marker::PhantomData; use std::os::unix::fs::MetadataExt; use std::path::Path; - use users::get_user_by_name; -use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct Owner<'a, C: CommandRunner, D: AsRef, U: AsRef> { - path: D, +#[derive(Debug)] +pub struct Owner<_C, C, P, U> { + path: P, user_name: U, - command_runner: &'a C, + command_runner: C, + phantom: PhantomData<_C>, } -impl<'a, C: CommandRunner, D: AsRef, U: AsRef> Owner<'a, C, D, U> { - pub fn new(path: D, user_name: U, command_runner: &'a C) -> Self { - Owner { +impl<_C, C, P, U> Owner<_C, C, P, U> { + pub fn new(path: P, user_name: U, command_runner: C) -> Self { + Self { path, user_name, command_runner, + phantom: PhantomData::default(), } } } -impl, U: AsRef> Symbol for Owner<'_, C, D, U> { +impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, U: AsRef> Symbol + for Owner<_C, C, P, U> +{ fn target_reached(&self) -> Result> { - if !Path::new(self.path.as_ref()).exists() { + if !self.path.as_ref().exists() { return Ok(false); } let actual_uid = fs::metadata(self.path.as_ref()).unwrap().uid(); @@ -38,35 +40,9 @@ impl, U: AsRef> Symbol for Owner<'_, C, D, } fn execute(&self) -> Result<(), Box> { - 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() + self.command_runner.borrow().run_successfully( + "chown", + args!["-R", self.user_name.as_ref(), self.path.as_ref()], ) } } diff --git a/src/symbols/postgresql/database.rs b/src/symbols/postgresql/database.rs index 1ef3728..7bc6c88 100644 --- a/src/symbols/postgresql/database.rs +++ b/src/symbols/postgresql/database.rs @@ -1,9 +1,8 @@ +use crate::command_runner::CommandRunner; +use crate::symbols::Symbol; use std::error::Error; use std::fmt; -use crate::command_runner::CommandRunner; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - pub struct PostgreSQLDatabase<'a, N: AsRef, S: AsRef, C: CommandRunner> { name: N, seed_file: S, @@ -85,17 +84,6 @@ 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/stored_directory.rs b/src/symbols/saved_directory.rs similarity index 52% rename from src/symbols/stored_directory.rs rename to src/symbols/saved_directory.rs index 827e807..81772bc 100644 --- a/src/symbols/stored_directory.rs +++ b/src/symbols/saved_directory.rs @@ -1,57 +1,50 @@ +use crate::command_runner::CommandRunner; +use crate::storage::Storage; +use crate::symbols::Symbol; +use std::borrow::Borrow; use std::error::Error; -use std::fmt; use std::fs; use std::io; +use std::marker::PhantomData; use std::path::Path; use std::str::FromStr; -use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::storage::Storage; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - #[derive(Debug, PartialEq)] pub enum StorageDirection { Load, - Save, + Store, } -pub struct StoredDirectory<'a, P: AsRef, S: Storage, C: CommandRunner> { +#[derive(Debug)] +pub struct SavedDirectory<_C, C, P, S> { path: P, storage: S, dir: StorageDirection, - command_runner: &'a C, + command_runner: C, + phantom: PhantomData<_C>, } -impl<'a, P: AsRef, S: Storage, C: CommandRunner> StoredDirectory<'a, P, S, C> { - pub fn new(path: P, storage: S, dir: StorageDirection, command_runner: &'a C) -> Self { - StoredDirectory { +impl<_C, C, P, S> SavedDirectory<_C, C, P, S> { + pub fn new(path: P, storage: S, dir: StorageDirection, command_runner: C) -> Self { + Self { path, storage, dir, command_runner, + phantom: PhantomData::default(), } } } -impl, 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> { +impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: Storage> Symbol + for SavedDirectory<_C, C, P, S> +{ 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::Save) + Ok(self.dir == StorageDirection::Store) } else { Err(Box::new(e)) }; @@ -64,7 +57,7 @@ impl, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_ } let dump_date = self.storage.recent_date()?; - let output = self.command_runner.get_output( + let output = self.command_runner.borrow().get_output( "sh", args![ "-c", @@ -75,12 +68,12 @@ impl, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_ ], )?; let modified_date = u64::from_str(String::from_utf8(output)?.trim_end())?; - if if self.dir == StorageDirection::Save { + if if self.dir == StorageDirection::Store { modified_date > dump_date } else { dump_date > modified_date } { - let output = self.command_runner.run_with_args( + let output = self.command_runner.borrow().run_with_args( "diff", args!["-rq", self.storage.read_filename()?, self.path.as_ref()], )?; @@ -98,51 +91,19 @@ impl, S: Storage, C: CommandRunner> Symbol for StoredDirectory<'_ if self.dir == StorageDirection::Load { self .command_runner + .borrow() .run_successfully("rm", args!["-rf", self.path.as_ref()])?; - self.command_runner.run_successfully( + self.command_runner.borrow().run_successfully( "cp", args!["-a", self.storage.read_filename()?, self.path.as_ref()], ) } else { - self.command_runner.run_successfully( + self.command_runner.borrow().run_successfully( "cp", args!["-a", self.path.as_ref(), self.storage.write_filename()], ) } } - - fn get_prerequisites(&self) -> Vec { - 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 687e04d..48167e6 100644 --- a/src/symbols/systemd/mod.rs +++ b/src/symbols/systemd/mod.rs @@ -1,3 +1,7 @@ -pub mod reload; -pub mod user_service; -pub mod user_session; +mod reload; +mod user_service; +mod user_session; + +pub use reload::ReloadService; +pub use user_service::UserService; +pub use user_session::UserSession; diff --git a/src/symbols/systemd/reload.rs b/src/symbols/systemd/reload.rs index cc1262c..36ea5ac 100644 --- a/src/symbols/systemd/reload.rs +++ b/src/symbols/systemd/reload.rs @@ -1,49 +1,35 @@ -use std::error::Error; -use std::fmt; - use crate::command_runner::CommandRunner; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; +use crate::symbols::Symbol; +use std::borrow::Borrow; +use std::error::Error; +use std::marker::PhantomData; -pub struct ReloadService<'a, S, C: CommandRunner> { +#[derive(Debug)] +pub struct ReloadService<_C, C, S> { service: S, - command_runner: &'a C, + command_runner: C, + phantom: PhantomData<_C>, } -impl<'a, S, C: CommandRunner> ReloadService<'a, S, C> { - pub fn new(service: S, command_runner: &'a C) -> Self { - ReloadService { +impl<_C, C, S> ReloadService<_C, C, S> { + pub fn new(command_runner: C, service: S) -> Self { + Self { service, command_runner, + phantom: PhantomData::default(), } } } -impl, C: CommandRunner> Symbol for ReloadService<'_, S, C> { +impl, _C: CommandRunner, C: Borrow<_C>> Symbol for ReloadService<_C, C, S> { fn target_reached(&self) -> Result> { Ok(true) } fn execute(&self) -> Result<(), Box> { - self.command_runner.run_successfully( + self.command_runner.borrow().run_successfully( "systemctl", args!["reload-or-restart", self.service.as_ref()], ) } - - fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { - 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 9d9eff0..5f2b31b 100644 --- a/src/symbols/systemd/user_service.rs +++ b/src/symbols/systemd/user_service.rs @@ -1,132 +1,34 @@ +use crate::command_runner::{CommandRunner, SetuidCommandRunner}; +use crate::symbols::Symbol; use std::error::Error; use std::ffi::OsStr; -use std::fmt; -use std::io; use std::path::Path; -use std::path::PathBuf; -use std::process::Output; use std::thread::sleep; use std::time::Duration; -use crate::command_runner::{CommandRunner, SetuidCommandRunner}; -use crate::resources::Resource; -use crate::symbols::file::File as FileSymbol; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - #[derive(Debug)] -pub enum UserServiceError { - ActivationFailed(io::Result), - ExecError(E), - GenericError, -} - -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 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> { +pub struct UserService<'a, S: AsRef, U: AsRef, R: CommandRunner> { socket_path: S, service_name: &'a str, - user_name: U, - command_runner: R, - config: FileSymbol, + command_runner: SetuidCommandRunner<'a, U, R>, } -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, - ) - } - +impl, U: AsRef, R: CommandRunner> UserService<'static, S, U, R> { pub fn new( socket_path: S, - home: &'_ Path, user_name: U, - service_name: &'a str, - command_runner: &'a R, - content: String, + service_name: &'static str, + command_runner: &'static R, ) -> Self { - let config_path: PathBuf = [ - home, - format!(".config/systemd/user/{}.service", service_name).as_ref(), - ] - .iter() - .collect(); - - UserService { + Self { socket_path, service_name, - user_name: user_name.clone(), command_runner: SetuidCommandRunner::new(user_name, command_runner), - config: FileSymbol::new(config_path, content), } } } -impl, U: AsRef, C: AsRef, R: CommandRunner> UserService<'_, S, U, C, R> { +impl, U: AsRef, R: CommandRunner> UserService<'_, S, U, R> { fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result> { let mut tries = 5; loop { @@ -161,12 +63,13 @@ impl, U: AsRef, C: AsRef, R: CommandRunner> UserService "ActiveState=activating" => sleep(Duration::from_millis(500)), "ActiveState=active" => return Ok(true), "ActiveState=failed" => { - return Err(Box::new( - UserServiceError::ActivationFailed(self.command_runner.run_with_args( + return Err( + String::from_utf8(self.command_runner.get_output( "journalctl", args!["--user", format!("--user-unit={}", self.service_name)], - )) as UserServiceError, - )) + )?)? + .into(), + ) } _ => return Ok(false), } @@ -174,26 +77,18 @@ impl, U: AsRef, C: AsRef, R: CommandRunner> UserService } } -impl, U: AsRef, C: AsRef, R: CommandRunner> Symbol - for UserService<'_, S, U, C, R> -{ +impl, U: AsRef, R: CommandRunner> Symbol for UserService<'_, S, U, 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(Box::new( - UserServiceError::GenericError as UserServiceError, - )); + return Err("Generic error".into()); } if self.socket_path.as_ref().exists() { @@ -202,32 +97,4 @@ impl, U: AsRef, C: AsRef, R: CommandRunner> Symbol 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 496d08e..c186ee9 100644 --- a/src/symbols/systemd/user_session.rs +++ b/src/symbols/systemd/user_session.rs @@ -1,55 +1,26 @@ -use std::error::Error; -use std::fmt; -use std::path::PathBuf; - use crate::command_runner::CommandRunner; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; +use crate::symbols::Symbol; +use std::error::Error; +use std::path::Path; #[derive(Debug)] -pub enum SystemdUserSessionError { - 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> { +pub struct UserSession<'a, U, C> { user_name: U, command_runner: &'a C, } -impl<'a, U: AsRef, C: CommandRunner> SystemdUserSession<'a, U, C> { +impl<'a, U, C> UserSession<'a, U, C> { pub fn new(user_name: U, command_runner: &'a C) -> Self { - SystemdUserSession { + Self { user_name, command_runner, } } } -impl, C: CommandRunner> Symbol for SystemdUserSession<'_, U, C> { +impl, C: CommandRunner> Symbol for UserSession<'_, U, C> { fn target_reached(&self) -> Result> { - let mut path = PathBuf::from("/var/lib/systemd/linger"); - path.push(self.user_name.as_ref()); + let path = Path::new("/var/lib/systemd/linger").join(self.user_name.as_ref()); Ok(path.exists()) // Could also do `loginctl show-user ${self.user_name} | grep -F 'Linger=yes` } @@ -59,21 +30,4 @@ impl, C: CommandRunner> Symbol for SystemdUserSession<'_, U, C> { .command_runner .run_successfully("loginctl", args!["enable-linger", self.user_name.as_ref()]) } - - fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { - 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 3b81566..e460ea0 100644 --- a/src/symbols/tls/csr.rs +++ b/src/symbols/tls/csr.rs @@ -1,56 +1,39 @@ -use std::borrow::Cow; -use std::error::Error; -use std::ffi::OsStr; -use std::fmt; -use std::path::PathBuf; - use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct TlsCsr<'a, C: CommandRunner> { - domain: Cow<'a, str>, - command_runner: &'a C, +use crate::symbols::Symbol; +use std::borrow::Borrow; +use std::error::Error; +use std::path::Path; + +#[derive(Debug)] +pub struct Csr { + command_runner: C, + domain: D, + key_path: K, + csr_path: P, } -impl<'a, C: CommandRunner> TlsCsr<'a, C> { - pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self { - TlsCsr { - domain, +impl Csr { + pub fn new(command_runner: C, domain: D, key_path: K, csr_path: P) -> Self { + Self { command_runner, + domain, + key_path, + csr_path, } } - - fn get_key_path(&self) -> PathBuf { - format!("/etc/ssl/private/{}.key", self.domain).into() - } - - fn get_csr_path(&self) -> PathBuf { - format!("/etc/ssl/local_certs/{}.csr", self.domain).into() - } } -impl fmt::Display for TlsCsr<'_, C> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "TlsCsr {}", self.domain) - } -} - -impl Symbol for TlsCsr<'_, C> { +impl, K: Borrow, P: Borrow> Symbol + for Csr +{ fn target_reached(&self) -> Result> { - if !self.get_csr_path().exists() { + if !self.csr_path.borrow().exists() { return Ok(false); } let output = self.command_runner.get_stderr( "openssl", - &[ - OsStr::new("req"), - "-in".as_ref(), - self.get_csr_path().as_ref(), - "-noout".as_ref(), - "-verify".as_ref(), - ], + args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",], )?; Ok(output == b"verify OK\n") } @@ -58,35 +41,20 @@ impl Symbol for TlsCsr<'_, C> { fn execute(&self) -> Result<(), Box> { self.command_runner.run_successfully( "openssl", - &[ - OsStr::new("req"), - "-new".as_ref(), - "-sha256".as_ref(), - "-key".as_ref(), - self.get_key_path().as_ref(), - "-out".as_ref(), - self.get_csr_path().as_ref(), - "-subj".as_ref(), - format!("/CN={}", self.domain).as_ref(), + args![ + "req", + "-new", + "-sha256", + "-key", + self.key_path.borrow(), + "-out", + self.csr_path.borrow(), + "-subj", + format!("/CN={}", self.domain.borrow()), ], )?; Ok(()) } - - fn get_prerequisites(&self) -> Vec { - 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 70a9b1b..4caff26 100644 --- a/src/symbols/tls/key.rs +++ b/src/symbols/tls/key.rs @@ -1,57 +1,42 @@ -use std::borrow::Cow; -use std::error::Error; -use std::ffi::OsStr; -use std::fmt; -use std::path::PathBuf; - use crate::command_runner::CommandRunner; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; +use crate::symbols::Symbol; +use std::error::Error; +use std::path::Path; -pub struct TlsKey<'a, C: CommandRunner> { - domain: Cow<'a, str>, - command_runner: &'a C, +#[derive(Debug)] +pub struct Key { + file_path: P, + command_runner: C, } -impl<'a, C: CommandRunner> TlsKey<'a, C> { - pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self { - TlsKey { - domain, +impl Key { + pub fn new(command_runner: C, file_path: P) -> Self { + Self { + file_path, command_runner, } } - fn get_path(&self) -> PathBuf { - ["/etc/ssl/private", &format!("{}.key", self.domain)] - .iter() - .collect() - } - fn get_bytes(&self) -> u32 { 4096 } } -impl fmt::Display for TlsKey<'_, C> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "TlsKey {}", self.domain) - } -} - -impl Symbol for TlsKey<'_, C> { +impl> Symbol for Key { fn target_reached(&self) -> Result> { - if !self.get_path().exists() { + if !self.file_path.as_ref().exists() { return Ok(false); } let stdout = self.command_runner.get_output( "openssl", - &[ - OsStr::new("rsa"), - "-in".as_ref(), - self.get_path().as_ref(), - "-noout".as_ref(), - "-check".as_ref(), - "-text".as_ref(), + args![ + "rsa", + "-in", + self.file_path.as_ref(), + "-noout", + "-check", + "-text", ], )?; // FIXME check bytes @@ -61,25 +46,14 @@ impl Symbol for TlsKey<'_, C> { fn execute(&self) -> Result<(), Box> { self.command_runner.run_successfully( "openssl", - &[ - OsStr::new("genrsa"), - "-out".as_ref(), - self.get_path().as_ref(), - self.get_bytes().to_string().as_ref(), + args![ + "genrsa", + "-out", + self.file_path.as_ref(), + self.get_bytes().to_string(), ], ) } - - fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { - 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 d7b4c79..b5d0041 100644 --- a/src/symbols/tls/mod.rs +++ b/src/symbols/tls/mod.rs @@ -1,7 +1,5 @@ mod csr; mod key; -mod self_signed_cert; -pub use self::csr::TlsCsr; -pub use self::key::TlsKey; -pub use self::self_signed_cert::SelfSignedTlsCert; +pub use self::csr::Csr; +pub use self::key::Key; diff --git a/src/symbols/tls/self_signed_cert.rs b/src/symbols/tls/self_signed_cert.rs deleted file mode 100644 index 71c0c4a..0000000 --- a/src/symbols/tls/self_signed_cert.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::error::Error; -use std::fmt; -use std::path::PathBuf; - -use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct SelfSignedTlsCert<'a, D: AsRef, 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 e3ca95d..a4d7ddd 100644 --- a/src/symbols/user.rs +++ b/src/symbols/user.rs @@ -1,97 +1,23 @@ -use std::borrow::Cow; -use std::error::Error; -use std::fmt; - use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; +use crate::symbols::Symbol; +use std::error::Error; #[derive(Debug)] -pub enum UserAdderError { - AlreadyExists, - UnknownError, - ImplError(Box), -} - -impl Error for UserAdderError { - fn description(&self) -> &str { - match self { - Self::AlreadyExists => "User already exists", - Self::UnknownError => "Unknown error", - Self::ImplError(_) => "User adding error", - } - } - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::ImplError(ref e) => Some(e.as_ref()), - _ => None, - } - } -} - -impl fmt::Display for UserAdderError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.source() { - Some(e) => write!(f, "{} (cause: {})", self.description(), e), - None => write!(f, "{}", self.description()), - } - } -} - -pub trait UserAdder { - fn add_user(&self, user_name: &str) -> Result<(), UserAdderError>; -} - -#[derive(Debug, PartialEq)] -pub enum UserError { - GenericError, -} - -impl Error for UserError { - fn description(&self) -> &str { - match self { - Self::GenericError => "Could not find out if user exists", - } - } - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - _ => None, - } - } -} - -impl fmt::Display for UserError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.source() { - Some(e) => write!(f, "{} (cause: {})", self.description(), e), - None => write!(f, "{}", self.description()), - } - } -} - -pub struct User<'a, C: CommandRunner, A: UserAdder> { - user_name: Cow<'a, str>, - command_runner: &'a C, - user_adder: &'a A, +pub struct User { + user_name: U, + command_runner: C, } -impl<'a, C: CommandRunner, A: 'a + UserAdder> User<'a, C, A> { - pub fn new(user_name: Cow<'a, str>, command_runner: &'a C, user_adder: &'a A) -> Self { - User { +impl User { + pub fn new(user_name: U, command_runner: C) -> Self { + Self { user_name, command_runner, - user_adder, } } } -impl<'a, C: CommandRunner, A: 'a + UserAdder> fmt::Display for User<'a, C, A> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "User {}", self.user_name) - } -} - -impl<'a, C: CommandRunner, A: 'a + UserAdder> Symbol for User<'a, C, A> { +impl, C: CommandRunner> Symbol for User { fn target_reached(&self) -> Result> { let output = self .command_runner @@ -99,108 +25,34 @@ impl<'a, C: CommandRunner, A: 'a + UserAdder> Symbol for User<'a, C, A> { match output.status.code() { Some(2) => Ok(false), Some(0) => Ok(true), - _ => Err(Box::new(UserError::GenericError)), + _ => Err("Unknown error".into()), } } fn execute(&self) -> Result<(), Box> { - 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( + self.command_runner.run_successfully( "adduser", args![ // "-m", // Necessary for Fedora, not accepted in Debian - "--system", user_name, + "--system", + self.user_name.as_ref(), ], - ); - match output { - Ok(output) => match output.status.code() { - Some(0) => Ok(()), - Some(1) => { - println!("{:?}", output); - Err(UserAdderError::AlreadyExists) - } - _ => { - println!("{:?}", output); - Err(UserAdderError::UnknownError) - } - }, - Err(e) => Err(UserAdderError::ImplError(Box::new(e))), - } + )?; + Ok(()) } } #[cfg(test)] mod test { - use std::error::Error; - use std::fmt; - use crate::command_runner::StdCommandRunner; use crate::symbols::user::User; - use crate::symbols::user::UserAdder; - use crate::symbols::user::UserAdderError; use crate::symbols::Symbol; - #[derive(Debug, PartialEq)] - struct DummyError; - impl Error for DummyError { - fn description(&self) -> &str { - "DummyError" - } - } - - impl fmt::Display for DummyError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "DummyError") - } - } - - struct DummyUserAdder; - - impl UserAdder for DummyUserAdder { - fn add_user(&self, _user_name: &str) -> Result<(), UserAdderError> { - Ok(()) - } - } - #[test] fn test_target_reached_nonexisting() { let symbol = User { - user_name: "nonexisting".into(), - command_runner: &StdCommandRunner, - user_adder: &DummyUserAdder, + user_name: "nonexisting", + command_runner: StdCommandRunner, }; assert_eq!(symbol.target_reached().unwrap(), false); } @@ -208,9 +60,8 @@ mod test { #[test] fn test_target_reached_root() { let symbol = User { - user_name: "root".into(), - command_runner: &StdCommandRunner, - user_adder: &DummyUserAdder, + user_name: "root", + command_runner: StdCommandRunner, }; assert_eq!(symbol.target_reached().unwrap(), true); } diff --git a/src/symbols/wordpress/mod.rs b/src/symbols/wordpress/mod.rs index e489e42..c2d705a 100644 --- a/src/symbols/wordpress/mod.rs +++ b/src/symbols/wordpress/mod.rs @@ -1,2 +1,5 @@ -pub mod plugin; -pub mod translation; +mod plugin; +mod translation; + +pub use plugin::Plugin; +pub use translation::Translation; diff --git a/src/symbols/wordpress/plugin.rs b/src/symbols/wordpress/plugin.rs index 1bdb1c9..123346c 100644 --- a/src/symbols/wordpress/plugin.rs +++ b/src/symbols/wordpress/plugin.rs @@ -1,24 +1,23 @@ use regex::Regex; use std::error::Error; -use std::fmt; use std::fs::File as FsFile; use std::io; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; +use crate::symbols::Symbol; -pub struct WordpressPlugin<'a, P: AsRef, N: AsRef, R: CommandRunner> { +#[derive(Debug)] +pub struct Plugin<'a, P, N, R> { base: P, name: N, command_runner: &'a R, } -impl<'a, P: AsRef, N: AsRef, R: CommandRunner> WordpressPlugin<'a, P, N, R> { +impl<'a, P: AsRef, N: AsRef, R: CommandRunner> Plugin<'a, P, N, R> { pub fn new(base: P, name: N, command_runner: &'a R) -> Self { - WordpressPlugin { + Self { base, name, command_runner, @@ -34,7 +33,7 @@ impl<'a, P: AsRef, N: AsRef, R: CommandRunner> WordpressPlugin<'a, P, } } -impl, N: AsRef, R: CommandRunner> Symbol for WordpressPlugin<'_, P, N, R> { +impl, N: AsRef, R: CommandRunner> Symbol for Plugin<'_, P, N, R> { fn target_reached(&self) -> Result> { let base_path = self.get_path(); if !base_path.exists() { @@ -99,30 +98,4 @@ impl, N: AsRef, R: CommandRunner> Symbol for WordpressPlugin 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 ea1aea2..15dacd2 100644 --- a/src/symbols/wordpress/translation.rs +++ b/src/symbols/wordpress/translation.rs @@ -1,31 +1,26 @@ +use crate::command_runner::CommandRunner; +use crate::symbols::Symbol; use regex::Regex; use std::cmp::max; use std::error::Error; - -use std::fmt; use std::fs::File as FsFile; use std::io; use std::io::{BufRead, BufReader}; use std::path::Path; use std::path::PathBuf; -use crate::command_runner::CommandRunner; -use crate::resources::Resource; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct WordpressTranslation<'a, C: AsRef, D: AsRef, R: CommandRunner> { +#[derive(Debug)] +pub struct Translation<'a, C, D, R> { path: D, version: &'a str, locale: C, command_runner: &'a R, } -impl<'a, C: AsRef, 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(), +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, version, locale, command_runner, @@ -33,7 +28,7 @@ impl<'a, C: AsRef, R: CommandRunner> WordpressTranslation<'a, C, PathBuf, R } } -impl, D: AsRef, R: CommandRunner> WordpressTranslation<'_, C, D, R> { +impl, D: AsRef, R: CommandRunner> Translation<'_, C, D, R> { fn get_pairs(&self) -> Vec<(String, PathBuf)> { let version_x = self .version @@ -64,7 +59,7 @@ impl, D: AsRef, R: CommandRunner> WordpressTranslation<'_, C } } -impl, D: AsRef, R: CommandRunner> Symbol for WordpressTranslation<'_, C, D, R> { +impl, D: AsRef, R: CommandRunner> Symbol for Translation<'_, 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(); @@ -115,27 +110,4 @@ impl, D: AsRef, R: CommandRunner> Symbol for WordpressTransl } 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 new file mode 100644 index 0000000..5115a02 --- /dev/null +++ b/src/templates/mod.rs @@ -0,0 +1,3 @@ +pub mod nginx; +pub mod php; +pub mod systemd; diff --git a/src/templates/nginx/mod.rs b/src/templates/nginx/mod.rs new file mode 100644 index 0000000..1134378 --- /dev/null +++ b/src/templates/nginx/mod.rs @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000..51178c4 --- /dev/null +++ b/src/templates/nginx/server.rs @@ -0,0 +1,202 @@ +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 new file mode 100644 index 0000000..87fc6bf --- /dev/null +++ b/src/templates/php.rs @@ -0,0 +1,24 @@ +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 new file mode 100644 index 0000000..2369489 --- /dev/null +++ b/src/templates/systemd.rs @@ -0,0 +1,45 @@ +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 new file mode 100644 index 0000000..2d20807 --- /dev/null +++ b/src/to_artifact.rs @@ -0,0 +1,25 @@ +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 new file mode 100644 index 0000000..0002462 --- /dev/null +++ b/static_files/lets_encrypt_x3_cross_signed.pem @@ -0,0 +1,27 @@ +-----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 d11172c..92210c5 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; +use std::path::{Path, PathBuf}; use tempdir::TempDir; fn get_dir(content: Option<&str>) -> TempDir { @@ -18,11 +18,8 @@ fn get_dir(content: Option<&str>) -> TempDir { tmp_dir } -fn get_symbol(path: &Path) -> FileSymbol<&str, String> { - FileSymbol::new( - String::from(path.join("filename").to_str().unwrap()), - "target content", - ) +fn get_symbol(path: &Path) -> FileSymbol { + FileSymbol::new(path.join("filename"), "target content") } // Normal cases @@ -81,7 +78,7 @@ fn no_file() { #[test] fn may_not_read_file() { - let symbol = get_symbol(&Path::new("/etc/passwd")); + let symbol = get_symbol(&Path::new("/etc/shadow")); assert_eq!(symbol.target_reached().is_err(), true); } diff --git a/tests/setup.rs b/tests/setup.rs new file mode 100644 index 0000000..b8aadf7 --- /dev/null +++ b/tests/setup.rs @@ -0,0 +1,72 @@ +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 964c3c5..cb4bc61 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.to_str().unwrap().into(), "filename".into()) + SimpleStorage::new(path.join("_filename")) } // Normal cases @@ -29,7 +29,7 @@ fn single_file() { assert!( Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())) .unwrap() - .is_match(&storage.write_filename()) + .is_match(storage.write_filename().to_str().unwrap()) ); 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()) + .is_match(storage.write_filename().to_str().unwrap()) ); 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()) + .is_match(storage.write_filename().to_str().unwrap()) ); 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()) + .is_match(storage.write_filename().to_str().unwrap()) ); 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()) + .is_match(storage.write_filename().to_str().unwrap()) ); 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()) + .is_match(storage.write_filename().to_str().unwrap()) ); 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()) + .is_match(storage.write_filename().to_str().unwrap()) ); assert!(storage.read_filename().is_err()); assert_eq!(