A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

479 lines
13 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. use crate::artifacts::{
  2. DatabaseName as DatabaseNameArtifact, Path as PathArtifact, ServiceName as ServiceNameArtifact,
  3. UserName as UserNameArtifact,
  4. };
  5. use crate::resources::{
  6. AcmeAccountKey, AcmeChallengesDir, AcmeChallengesNginxSnippet, AcmeRootCert, AcmeUser, Cert,
  7. CertChain, Cron, Csr, DefaultServer, Dir, File, GitCheckout, Key, KeyAndCertBundle,
  8. LoadedDirectory, MariaDbDatabase, MariaDbUser, NpmInstall, Owner, PhpFpmPool, PostgresqlDatabase,
  9. Resource, ServeCustom, ServePhp, ServeRedir, ServeService, ServeStatic, StoredDirectory,
  10. SystemdSocketService, User, UserForDomain, WordpressPlugin, WordpressTranslation,
  11. };
  12. use crate::to_artifact::ToArtifact;
  13. use std::fmt::Display;
  14. use std::marker::PhantomData;
  15. use std::path::{Path, PathBuf};
  16. pub trait Policy {
  17. #[must_use]
  18. fn acme_user() -> &'static str {
  19. "acme"
  20. }
  21. #[must_use]
  22. fn user_home(user_name: &str) -> PathBuf {
  23. Path::new("/home").join(user_name)
  24. }
  25. #[must_use]
  26. fn user_name_for_domain(domain_name: &'_ str) -> String {
  27. domain_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part)
  28. }
  29. #[must_use]
  30. fn php_version() -> &'static str {
  31. "7.0"
  32. }
  33. #[must_use]
  34. fn path_for_data(name: impl Display) -> PathBuf {
  35. Path::new("/root/data").join(format!("_{}", name))
  36. }
  37. }
  38. #[derive(Debug)]
  39. pub struct DefaultPolicy;
  40. impl Policy for DefaultPolicy {}
  41. pub trait ResourceLocator<R> {
  42. type Prerequisites: ToArtifact;
  43. fn locate(resource: &R) -> (R::Artifact, Self::Prerequisites)
  44. where
  45. R: Resource;
  46. }
  47. #[derive(Debug)]
  48. pub struct DefaultLocator<P = DefaultPolicy> {
  49. phantom: PhantomData<P>,
  50. }
  51. impl<P, D: AsRef<str>> ResourceLocator<Key<D>> for DefaultLocator<P> {
  52. type Prerequisites = Dir<PathBuf>;
  53. fn locate(resource: &Key<D>) -> (<Key<D> as Resource>::Artifact, Self::Prerequisites) {
  54. (
  55. PathArtifact::from(format!("/etc/ssl/private/{}.key", resource.0.as_ref())),
  56. Dir("/etc/ssl/private".into()),
  57. )
  58. }
  59. }
  60. impl<P, D: AsRef<str>> ResourceLocator<Csr<D>> for DefaultLocator<P> {
  61. type Prerequisites = Dir<PathBuf>;
  62. fn locate(resource: &Csr<D>) -> (<Csr<D> as Resource>::Artifact, Self::Prerequisites) {
  63. (
  64. PathArtifact::from(format!("/etc/ssl/local_certs/{}.csr", resource.0.as_ref())),
  65. Dir("/etc/ssl/local_certs".into()),
  66. )
  67. }
  68. }
  69. impl<P, D: AsRef<str>> ResourceLocator<Cert<D>> for DefaultLocator<P> {
  70. type Prerequisites = Dir<PathBuf>;
  71. fn locate(resource: &Cert<D>) -> (<Cert<D> as Resource>::Artifact, Self::Prerequisites) {
  72. (
  73. PathArtifact::from(format!("/etc/ssl/local_certs/{}.crt", resource.0.as_ref())),
  74. Dir("/etc/ssl/local_certs".into()),
  75. )
  76. }
  77. }
  78. impl<P, D: AsRef<str>> ResourceLocator<CertChain<D>> for DefaultLocator<P> {
  79. type Prerequisites = Dir<PathBuf>;
  80. fn locate(
  81. resource: &CertChain<D>,
  82. ) -> (<CertChain<D> as Resource>::Artifact, Self::Prerequisites) {
  83. (
  84. PathArtifact::from(format!(
  85. "/etc/ssl/local_certs/{}.chained.crt",
  86. resource.0.as_ref()
  87. )),
  88. Dir("/etc/ssl/local_certs".into()),
  89. )
  90. }
  91. }
  92. impl<P, D: AsRef<str>> ResourceLocator<KeyAndCertBundle<D>> for DefaultLocator<P> {
  93. type Prerequisites = Dir<PathBuf>;
  94. fn locate(
  95. resource: &KeyAndCertBundle<D>,
  96. ) -> (
  97. <KeyAndCertBundle<D> as Resource>::Artifact,
  98. Self::Prerequisites,
  99. ) {
  100. (
  101. PathArtifact::from(format!(
  102. "/etc/ssl/private/{}.with_key.crt",
  103. resource.0.as_ref()
  104. )),
  105. Dir("/etc/ssl/private".into()),
  106. )
  107. }
  108. }
  109. impl<'a, POLICY, P: AsRef<Path>> ResourceLocator<File<P>> for DefaultLocator<POLICY> {
  110. type Prerequisites = Dir<PathBuf>;
  111. fn locate(resource: &File<P>) -> (<File<P> as Resource>::Artifact, Self::Prerequisites) {
  112. ((), Dir(resource.0.as_ref().parent().unwrap().into()))
  113. }
  114. }
  115. impl<'a, POLICY, P: AsRef<Path>> ResourceLocator<GitCheckout<'a, P>> for DefaultLocator<POLICY> {
  116. type Prerequisites = Dir<PathBuf>;
  117. fn locate(
  118. resource: &GitCheckout<'a, P>,
  119. ) -> (
  120. <GitCheckout<'a, P> as Resource>::Artifact,
  121. Self::Prerequisites,
  122. ) {
  123. ((), Dir(resource.0.as_ref().parent().unwrap().into()))
  124. }
  125. }
  126. impl<POLICY, P: Clone + AsRef<Path>> ResourceLocator<Dir<P>> for DefaultLocator<POLICY> {
  127. type Prerequisites = Option<Dir<PathBuf>>;
  128. fn locate(resource: &Dir<P>) -> (<Dir<P> as Resource>::Artifact, Self::Prerequisites) {
  129. ((), resource.0.as_ref().parent().map(|p| Dir(p.into())))
  130. }
  131. }
  132. impl<POLICY, P> ResourceLocator<NpmInstall<P>> for DefaultLocator<POLICY> {
  133. type Prerequisites = ();
  134. fn locate(
  135. _resource: &NpmInstall<P>,
  136. ) -> (<NpmInstall<P> as Resource>::Artifact, Self::Prerequisites) {
  137. ((), ())
  138. }
  139. }
  140. impl<POLICY: Policy, P: AsRef<Path>> ResourceLocator<StoredDirectory<P>>
  141. for DefaultLocator<POLICY>
  142. {
  143. type Prerequisites = ();
  144. fn locate(
  145. resource: &StoredDirectory<P>,
  146. ) -> (
  147. <StoredDirectory<P> as Resource>::Artifact,
  148. Self::Prerequisites,
  149. ) {
  150. (PathArtifact::from(POLICY::path_for_data(resource.0)), ())
  151. }
  152. }
  153. impl<POLICY: Policy, P: AsRef<Path>> ResourceLocator<LoadedDirectory<P>>
  154. for DefaultLocator<POLICY>
  155. {
  156. type Prerequisites = Dir<PathBuf>;
  157. fn locate(
  158. resource: &LoadedDirectory<P>,
  159. ) -> (
  160. <LoadedDirectory<P> as Resource>::Artifact,
  161. Self::Prerequisites,
  162. ) {
  163. (
  164. PathArtifact::from(POLICY::path_for_data(resource.0)),
  165. Dir(resource.1.as_ref().parent().unwrap().into()),
  166. )
  167. }
  168. }
  169. impl<P: Policy> ResourceLocator<AcmeAccountKey> for DefaultLocator<P> {
  170. type Prerequisites = Dir<PathBuf>;
  171. fn locate(
  172. _resource: &AcmeAccountKey,
  173. ) -> (<AcmeAccountKey as Resource>::Artifact, Self::Prerequisites) {
  174. let acme_user = P::acme_user();
  175. let home = P::user_home(acme_user);
  176. (PathArtifact::from(home.join("account.key")), Dir(home))
  177. }
  178. }
  179. impl<P: Policy> ResourceLocator<AcmeUser> for DefaultLocator<P> {
  180. type Prerequisites = ();
  181. fn locate(_resource: &AcmeUser) -> (<AcmeUser as Resource>::Artifact, Self::Prerequisites) {
  182. let acme_user = P::acme_user();
  183. (UserNameArtifact(acme_user.into()), ())
  184. }
  185. }
  186. impl<P: Policy> ResourceLocator<AcmeChallengesDir> for DefaultLocator<P> {
  187. type Prerequisites = Dir<PathBuf>;
  188. fn locate(
  189. _resource: &AcmeChallengesDir,
  190. ) -> (
  191. <AcmeChallengesDir as Resource>::Artifact,
  192. Self::Prerequisites,
  193. ) {
  194. let acme_user = P::acme_user();
  195. let home = P::user_home(acme_user);
  196. (PathArtifact::from(home.join("challenges")), Dir(home))
  197. }
  198. }
  199. impl<P: Policy> ResourceLocator<AcmeChallengesNginxSnippet> for DefaultLocator<P> {
  200. type Prerequisites = ();
  201. fn locate(
  202. _resource: &AcmeChallengesNginxSnippet,
  203. ) -> (
  204. <AcmeChallengesNginxSnippet as Resource>::Artifact,
  205. Self::Prerequisites,
  206. ) {
  207. (
  208. PathArtifact::from("/etc/nginx/snippets/acme-challenge.conf"),
  209. (),
  210. )
  211. }
  212. }
  213. impl<P: Policy> ResourceLocator<AcmeRootCert> for DefaultLocator<P> {
  214. type Prerequisites = Dir<PathBuf>;
  215. fn locate(
  216. _resource: &AcmeRootCert,
  217. ) -> (<AcmeRootCert as Resource>::Artifact, Self::Prerequisites) {
  218. let acme_user = P::acme_user();
  219. let home = P::user_home(acme_user);
  220. (
  221. PathArtifact::from(home.join("lets_encrypt_r3.pem")),
  222. Dir(home),
  223. )
  224. }
  225. }
  226. impl<P: Policy, D: AsRef<str>> ResourceLocator<UserForDomain<D>> for DefaultLocator<P> {
  227. type Prerequisites = ();
  228. fn locate(
  229. resource: &UserForDomain<D>,
  230. ) -> (
  231. <UserForDomain<D> as Resource>::Artifact,
  232. Self::Prerequisites,
  233. ) {
  234. let user_name = P::user_name_for_domain(resource.0.as_ref());
  235. let home = P::user_home(&user_name);
  236. ((UserNameArtifact(user_name), PathArtifact::from(home)), ())
  237. }
  238. }
  239. impl<P> ResourceLocator<User> for DefaultLocator<P> {
  240. type Prerequisites = ();
  241. fn locate(_resource: &User) -> (<User as Resource>::Artifact, Self::Prerequisites) {
  242. ((), ())
  243. }
  244. }
  245. impl<P, POLICY> ResourceLocator<Owner<P>> for DefaultLocator<POLICY> {
  246. type Prerequisites = User;
  247. fn locate(resource: &Owner<P>) -> (<Owner<P> as Resource>::Artifact, Self::Prerequisites) {
  248. ((), User(resource.0.clone()))
  249. }
  250. }
  251. impl<P> ResourceLocator<DefaultServer> for DefaultLocator<P> {
  252. type Prerequisites = ();
  253. fn locate(
  254. _resource: &DefaultServer,
  255. ) -> (<DefaultServer as Resource>::Artifact, Self::Prerequisites) {
  256. (PathArtifact::from("/etc/nginx/sites-enabled/default"), ())
  257. }
  258. }
  259. impl<D: AsRef<Path>, POLICY> ResourceLocator<ServeCustom<D>> for DefaultLocator<POLICY> {
  260. type Prerequisites = ();
  261. fn locate(
  262. resource: &ServeCustom<D>,
  263. ) -> (<ServeCustom<D> as Resource>::Artifact, Self::Prerequisites) {
  264. (
  265. PathArtifact::from(Path::new("/etc/nginx/sites-enabled/").join(&resource.0)),
  266. (),
  267. )
  268. }
  269. }
  270. impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServePhp<D, P>> for DefaultLocator<POLICY> {
  271. type Prerequisites = ();
  272. fn locate(
  273. resource: &ServePhp<D, P>,
  274. ) -> (<ServePhp<D, P> as Resource>::Artifact, Self::Prerequisites) {
  275. (
  276. PathArtifact::from(Path::new("/etc/nginx/sites-enabled/").join(&resource.0)),
  277. (),
  278. )
  279. }
  280. }
  281. impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServeService<D, P>> for DefaultLocator<POLICY> {
  282. type Prerequisites = ();
  283. fn locate(
  284. resource: &ServeService<D, P>,
  285. ) -> (
  286. <ServeService<D, P> as Resource>::Artifact,
  287. Self::Prerequisites,
  288. ) {
  289. (
  290. PathArtifact::from(Path::new("/etc/nginx/sites-enabled/").join(&resource.0)),
  291. (),
  292. )
  293. }
  294. }
  295. impl<D: AsRef<Path>, POLICY> ResourceLocator<ServeRedir<D>> for DefaultLocator<POLICY> {
  296. type Prerequisites = ();
  297. fn locate(
  298. resource: &ServeRedir<D>,
  299. ) -> (<ServeRedir<D> as Resource>::Artifact, Self::Prerequisites) {
  300. (
  301. PathArtifact::from(Path::new("/etc/nginx/sites-enabled/").join(&resource.0)),
  302. (),
  303. )
  304. }
  305. }
  306. impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServeStatic<D, P>> for DefaultLocator<POLICY> {
  307. type Prerequisites = ();
  308. fn locate(
  309. resource: &ServeStatic<D, P>,
  310. ) -> (
  311. <ServeStatic<D, P> as Resource>::Artifact,
  312. Self::Prerequisites,
  313. ) {
  314. (
  315. PathArtifact::from(Path::new("/etc/nginx/sites-enabled/").join(&resource.0)),
  316. (),
  317. )
  318. }
  319. }
  320. impl<D: Clone + AsRef<str>, P: Policy> ResourceLocator<PhpFpmPool<D>> for DefaultLocator<P> {
  321. type Prerequisites = ();
  322. fn locate(
  323. resource: &PhpFpmPool<D>,
  324. ) -> (<PhpFpmPool<D> as Resource>::Artifact, Self::Prerequisites) {
  325. let ((user, _), ()) = Self::locate(&UserForDomain(&resource.0));
  326. let php_version = P::php_version();
  327. (
  328. (
  329. PathArtifact::from(format!("/run/php/{}.sock", user.0)),
  330. PathArtifact::from(format!(
  331. "/etc/php/{}/fpm/pool.d/{}.conf",
  332. php_version, user.0
  333. )),
  334. user,
  335. ServiceNameArtifact(format!("php{}-fpm", php_version)),
  336. ),
  337. (),
  338. )
  339. }
  340. }
  341. impl<D: Clone + AsRef<str>, P, POLICY: Policy> ResourceLocator<SystemdSocketService<D, P>>
  342. for DefaultLocator<POLICY>
  343. {
  344. type Prerequisites = Dir<PathBuf>;
  345. fn locate(
  346. resource: &SystemdSocketService<D, P>,
  347. ) -> (
  348. <SystemdSocketService<D, P> as Resource>::Artifact,
  349. Self::Prerequisites,
  350. ) {
  351. let ((user_name, home_path), ()) = Self::locate(&UserForDomain(&resource.0));
  352. let config = home_path.as_ref().join(".config");
  353. let service_dir_path = config.join("systemd/user");
  354. (
  355. (
  356. PathArtifact::from(format!("/var/tmp/{}-{}.socket", user_name.0, resource.1)),
  357. PathArtifact::from(service_dir_path.join(format!("{}.service", resource.1))),
  358. user_name,
  359. ),
  360. Dir(service_dir_path),
  361. )
  362. }
  363. }
  364. impl<D: AsRef<str>, P: Policy> ResourceLocator<MariaDbDatabase<D>> for DefaultLocator<P> {
  365. type Prerequisites = ();
  366. fn locate(
  367. resource: &MariaDbDatabase<D>,
  368. ) -> (
  369. <MariaDbDatabase<D> as Resource>::Artifact,
  370. Self::Prerequisites,
  371. ) {
  372. let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0));
  373. (
  374. (
  375. DatabaseNameArtifact(user_name.0.clone()),
  376. user_name.clone(),
  377. PathArtifact::from(P::path_for_data(format!("{}.sql", user_name.0))),
  378. ),
  379. (),
  380. )
  381. }
  382. }
  383. impl<D: AsRef<str>, P: Policy> ResourceLocator<MariaDbUser<D>> for DefaultLocator<P> {
  384. type Prerequisites = ();
  385. fn locate(
  386. resource: &MariaDbUser<D>,
  387. ) -> (<MariaDbUser<D> as Resource>::Artifact, Self::Prerequisites) {
  388. let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0));
  389. ((user_name), ())
  390. }
  391. }
  392. impl<D: AsRef<str>, P: Policy> ResourceLocator<PostgresqlDatabase<D>> for DefaultLocator<P> {
  393. type Prerequisites = ();
  394. fn locate(
  395. resource: &PostgresqlDatabase<D>,
  396. ) -> (
  397. <PostgresqlDatabase<D> as Resource>::Artifact,
  398. Self::Prerequisites,
  399. ) {
  400. let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0));
  401. (
  402. (
  403. DatabaseNameArtifact(user_name.0.clone()),
  404. PathArtifact::from(P::path_for_data(format!("{}.sql", user_name.0))),
  405. ),
  406. (),
  407. )
  408. }
  409. }
  410. impl<P, POLICY> ResourceLocator<WordpressPlugin<P>> for DefaultLocator<POLICY> {
  411. type Prerequisites = ();
  412. fn locate(
  413. _resource: &WordpressPlugin<P>,
  414. ) -> (
  415. <WordpressPlugin<P> as Resource>::Artifact,
  416. Self::Prerequisites,
  417. ) {
  418. ((), ())
  419. }
  420. }
  421. impl<P, POLICY> ResourceLocator<WordpressTranslation<P>> for DefaultLocator<POLICY> {
  422. type Prerequisites = ();
  423. fn locate(
  424. _resource: &WordpressTranslation<P>,
  425. ) -> (
  426. <WordpressTranslation<P> as Resource>::Artifact,
  427. Self::Prerequisites,
  428. ) {
  429. ((), ())
  430. }
  431. }
  432. impl<D, P> ResourceLocator<Cron<D>> for DefaultLocator<P> {
  433. type Prerequisites = ();
  434. fn locate(_resource: &Cron<D>) -> (<Cron<D> as Resource>::Artifact, Self::Prerequisites) {
  435. ((), ())
  436. }
  437. }