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.

485 lines
14 KiB

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