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

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, PostgresqlDatabase,
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;
use std::rc::Rc;
pub trait Policy {
#[must_use]
fn acme_user() -> &'static str {
"acme"
}
#[must_use]
fn user_home(user_name: &str) -> Rc<Path> {
Path::new("/home").join(user_name).into()
}
#[must_use]
fn user_name_for_domain(domain_name: &'_ str) -> Rc<str> {
domain_name.split('.').rev().fold(String::new(), |result, part| if result.is_empty() { result } else { result + "_" } + part).into()
}
#[must_use]
fn php_version() -> &'static str {
"7.0"
}
#[must_use]
fn path_for_data(name: impl Display) -> Rc<Path> {
Path::new("/root/data").join(format!("_{name}")).into()
}
}
#[derive(Debug)]
pub struct DefaultPolicy;
impl Policy for DefaultPolicy {}
pub trait ResourceLocator<R> {
type Prerequisites: ToArtifact;
fn locate(resource: &R) -> (R::Artifact, Self::Prerequisites)
where
R: Resource;
}
#[derive(Debug)]
pub struct DefaultLocator<P = DefaultPolicy> {
phantom: PhantomData<P>,
}
impl<P, D: AsRef<str>> ResourceLocator<Key<D>> for DefaultLocator<P> {
type Prerequisites = Dir<Rc<Path>>;
fn locate(resource: &Key<D>) -> (<Key<D> as Resource>::Artifact, Self::Prerequisites) {
(
PathArtifact::from(format!("/etc/ssl/private/{}.key", resource.0.as_ref())),
Dir::new("/etc/ssl/private"),
)
}
}
impl<P, D: AsRef<str>> ResourceLocator<Csr<D>> for DefaultLocator<P> {
type Prerequisites = Dir<Rc<Path>>;
fn locate(resource: &Csr<D>) -> (<Csr<D> as Resource>::Artifact, Self::Prerequisites) {
(
PathArtifact::from(format!("/etc/ssl/local_certs/{}.csr", resource.0.as_ref())),
Dir::new("/etc/ssl/local_certs"),
)
}
}
impl<P, D: AsRef<str>> ResourceLocator<Cert<D>> for DefaultLocator<P> {
type Prerequisites = Dir<Rc<Path>>;
fn locate(resource: &Cert<D>) -> (<Cert<D> as Resource>::Artifact, Self::Prerequisites) {
(
PathArtifact::from(format!("/etc/ssl/local_certs/{}.crt", resource.0.as_ref())),
Dir::new("/etc/ssl/local_certs"),
)
}
}
impl<P, D: AsRef<str>> ResourceLocator<CertChain<D>> for DefaultLocator<P> {
type Prerequisites = Dir<Rc<Path>>;
fn locate(
resource: &CertChain<D>,
) -> (<CertChain<D> as Resource>::Artifact, Self::Prerequisites) {
(
PathArtifact::from(format!(
"/etc/ssl/local_certs/{}.chained.crt",
resource.0.as_ref()
)),
Dir::new("/etc/ssl/local_certs"),
)
}
}
impl<P, D: AsRef<str>> ResourceLocator<KeyAndCertBundle<D>> for DefaultLocator<P> {
type Prerequisites = Dir<Rc<Path>>;
fn locate(
resource: &KeyAndCertBundle<D>,
) -> (
<KeyAndCertBundle<D> as Resource>::Artifact,
Self::Prerequisites,
) {
(
PathArtifact::from(format!(
"/etc/ssl/private/{}.with_key.crt",
resource.0.as_ref()
)),
Dir::new("/etc/ssl/private"),
)
}
}
impl<POLICY, P: AsRef<Path>> ResourceLocator<File<P>> for DefaultLocator<POLICY> {
type Prerequisites = Dir<Rc<Path>>;
fn locate(resource: &File<P>) -> (<File<P> as Resource>::Artifact, Self::Prerequisites) {
((), Dir::new(resource.0.as_ref().parent().unwrap()))
}
}
impl<'a, POLICY, P: AsRef<Path>> ResourceLocator<GitCheckout<'a, P>> for DefaultLocator<POLICY> {
type Prerequisites = Dir<Rc<Path>>;
fn locate(
resource: &GitCheckout<'a, P>,
) -> (
<GitCheckout<'a, P> as Resource>::Artifact,
Self::Prerequisites,
) {
((), Dir::new(resource.0.as_ref().parent().unwrap()))
}
}
impl<POLICY, P: Clone + AsRef<Path>> ResourceLocator<Dir<P>> for DefaultLocator<POLICY> {
type Prerequisites = Option<Dir<Rc<Path>>>;
fn locate(resource: &Dir<P>) -> (<Dir<P> as Resource>::Artifact, Self::Prerequisites) {
((), resource.0.as_ref().parent().map(Dir::new))
}
}
impl<POLICY, P> ResourceLocator<NpmInstall<P>> for DefaultLocator<POLICY> {
type Prerequisites = ();
fn locate(
_resource: &NpmInstall<P>,
) -> (<NpmInstall<P> as Resource>::Artifact, Self::Prerequisites) {
((), ())
}
}
impl<POLICY: Policy, P: AsRef<Path>> ResourceLocator<StoredDirectory<P>>
for DefaultLocator<POLICY>
{
type Prerequisites = ();
fn locate(
resource: &StoredDirectory<P>,
) -> (
<StoredDirectory<P> as Resource>::Artifact,
Self::Prerequisites,
) {
(PathArtifact::from(POLICY::path_for_data(resource.0)), ())
}
}
impl<POLICY: Policy, P: AsRef<Path>> ResourceLocator<LoadedDirectory<P>>
for DefaultLocator<POLICY>
{
type Prerequisites = Dir<Rc<Path>>;
fn locate(
resource: &LoadedDirectory<P>,
) -> (
<LoadedDirectory<P> as Resource>::Artifact,
Self::Prerequisites,
) {
(
PathArtifact::from(POLICY::path_for_data(resource.0)),
Dir::new(resource.1.as_ref().parent().unwrap()),
)
}
}
impl<P: Policy> ResourceLocator<AcmeAccountKey> for DefaultLocator<P> {
type Prerequisites = Dir<Rc<Path>>;
fn locate(
_resource: &AcmeAccountKey,
) -> (<AcmeAccountKey as Resource>::Artifact, Self::Prerequisites) {
let acme_user = P::acme_user();
let home = P::user_home(acme_user);
(PathArtifact::from(home.join("account.key")), Dir(home))
}
}
impl<P: Policy> ResourceLocator<AcmeUser> for DefaultLocator<P> {
type Prerequisites = ();
fn locate(_resource: &AcmeUser) -> (<AcmeUser as Resource>::Artifact, Self::Prerequisites) {
let user_name = P::acme_user();
let home = P::user_home(&user_name);
((UserNameArtifact(user_name.into()), PathArtifact::from(home)), ())
}
}
impl<P: Policy> ResourceLocator<AcmeChallengesDir> for DefaultLocator<P> {
type Prerequisites = Dir<Rc<Path>>;
fn locate(
_resource: &AcmeChallengesDir,
) -> (
<AcmeChallengesDir as Resource>::Artifact,
Self::Prerequisites,
) {
let acme_user = P::acme_user();
let home = P::user_home(acme_user);
(PathArtifact::from(home.join("challenges")), Dir(home))
}
}
impl<P: Policy> ResourceLocator<AcmeChallengesNginxSnippet> for DefaultLocator<P> {
type Prerequisites = ();
fn locate(
_resource: &AcmeChallengesNginxSnippet,
) -> (
<AcmeChallengesNginxSnippet as Resource>::Artifact,
Self::Prerequisites,
) {
(
PathArtifact::from("/etc/nginx/snippets/acme-challenge.conf"),
(),
)
}
}
impl<P: Policy> ResourceLocator<AcmeRootCert> for DefaultLocator<P> {
type Prerequisites = Dir<Rc<Path>>;
fn locate(
_resource: &AcmeRootCert,
) -> (<AcmeRootCert as Resource>::Artifact, Self::Prerequisites) {
let acme_user = P::acme_user();
let home = P::user_home(acme_user);
(
PathArtifact::from(home.join("lets_encrypt_r3.pem")),
Dir(home),
)
}
}
impl<P: Policy, D: AsRef<str>> ResourceLocator<UserForDomain<D>> for DefaultLocator<P> {
type Prerequisites = ();
fn locate(
resource: &UserForDomain<D>,
) -> (
<UserForDomain<D> 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::from(home)), ())
}
}
impl<P: Policy> ResourceLocator<User> for DefaultLocator<P> {
type Prerequisites = ();
fn locate(resource: &User) -> (<User as Resource>::Artifact, Self::Prerequisites) {
let home = P::user_home(&resource.0);
((PathArtifact::from(home)), ())
}
}
impl<P, POLICY> ResourceLocator<Owner<P>> for DefaultLocator<POLICY> {
type Prerequisites = User;
fn locate(resource: &Owner<P>) -> (<Owner<P> as Resource>::Artifact, Self::Prerequisites) {
((), User(resource.0.clone()))
}
}
impl<P> ResourceLocator<DefaultServer> for DefaultLocator<P> {
type Prerequisites = ();
fn locate(
_resource: &DefaultServer,
) -> (<DefaultServer as Resource>::Artifact, Self::Prerequisites) {
(PathArtifact::from("/etc/nginx/sites-enabled/default"), ())
}
}
impl<D: AsRef<Path>, POLICY> ResourceLocator<ServeCustom<D>> for DefaultLocator<POLICY> {
type Prerequisites = ();
fn locate(
resource: &ServeCustom<D>,
) -> (<ServeCustom<D> as Resource>::Artifact, Self::Prerequisites) {
(
PathArtifact::from(Path::new("/etc/nginx/sites-enabled/").join(&resource.0)),
(),
)
}
}
impl<D: AsRef<Path>, P, C, POLICY> ResourceLocator<ServePhp<D, P, C>> for DefaultLocator<POLICY> {
type Prerequisites = ();
fn locate(
resource: &ServePhp<D, P, C>,
) -> (
<ServePhp<D, P, C> as Resource>::Artifact,
Self::Prerequisites,
) {
(
PathArtifact::from(Path::new("/etc/nginx/sites-enabled/").join(&resource.0)),
(),
)
}
}
impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServeService<D, P>> for DefaultLocator<POLICY> {
type Prerequisites = ();
fn locate(
resource: &ServeService<D, P>,
) -> (
<ServeService<D, P> as Resource>::Artifact,
Self::Prerequisites,
) {
(
PathArtifact::from(Path::new("/etc/nginx/sites-enabled/").join(&resource.0)),
(),
)
}
}
impl<D: AsRef<Path>, POLICY> ResourceLocator<ServeRedir<D>> for DefaultLocator<POLICY> {
type Prerequisites = ();
fn locate(
resource: &ServeRedir<D>,
) -> (<ServeRedir<D> as Resource>::Artifact, Self::Prerequisites) {
(
PathArtifact::from(Path::new("/etc/nginx/sites-enabled/").join(&resource.0)),
(),
)
}
}
impl<D: AsRef<Path>, P, POLICY> ResourceLocator<ServeStatic<D, P>> for DefaultLocator<POLICY> {
type Prerequisites = ();
fn locate(
resource: &ServeStatic<D, P>,
) -> (
<ServeStatic<D, P> as Resource>::Artifact,
Self::Prerequisites,
) {
(
PathArtifact::from(Path::new("/etc/nginx/sites-enabled/").join(&resource.0)),
(),
)
}
}
impl<D: Clone + AsRef<str>, P: Policy> ResourceLocator<PhpFpmPool<D>> for DefaultLocator<P> {
type Prerequisites = ();
fn locate(
resource: &PhpFpmPool<D>,
) -> (<PhpFpmPool<D> as Resource>::Artifact, Self::Prerequisites) {
let ((user, _), ()) = Self::locate(&UserForDomain(&resource.0));
let php_version = P::php_version();
(
(
PathArtifact::from(format!("/run/php/{}.sock", user.0)),
PathArtifact::from(format!(
"/etc/php/{}/fpm/pool.d/{}.conf",
php_version, user.0
)),
user,
ServiceNameArtifact(format!("php{php_version}-fpm").into()),
),
(),
)
}
}
impl<D: Clone + AsRef<str>, P, POLICY: Policy> ResourceLocator<SystemdSocketService<D, P>>
for DefaultLocator<POLICY>
{
type Prerequisites = Dir<Rc<Path>>;
fn locate(
resource: &SystemdSocketService<D, P>,
) -> (
<SystemdSocketService<D, P> as Resource>::Artifact,
Self::Prerequisites,
) {
let ((user_name, home_path), ()) = Self::locate(&UserForDomain(&resource.0));
let config = home_path.as_ref().join(".config");
let service_dir_path = config.join("systemd/user");
(
(
PathArtifact::from(format!("/var/tmp/{}-{}.socket", user_name.0, resource.1)),
PathArtifact::from(service_dir_path.join(format!("{}.service", resource.1))),
user_name,
),
Dir::new(service_dir_path),
)
}
}
impl<D: AsRef<str>, P: Policy> ResourceLocator<MariaDbDatabase<D>> for DefaultLocator<P> {
type Prerequisites = ();
fn locate(
resource: &MariaDbDatabase<D>,
) -> (
<MariaDbDatabase<D> as Resource>::Artifact,
Self::Prerequisites,
) {
let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0));
(
(
DatabaseNameArtifact(user_name.0.clone()),
user_name.clone(),
PathArtifact::from(P::path_for_data(format!("{}.sql", user_name.0))),
),
(),
)
}
}
impl<D: AsRef<str>, P: Policy> ResourceLocator<MariaDbUser<D>> for DefaultLocator<P> {
type Prerequisites = ();
fn locate(
resource: &MariaDbUser<D>,
) -> (<MariaDbUser<D> as Resource>::Artifact, Self::Prerequisites) {
let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0));
((user_name), ())
}
}
impl<D: AsRef<str>, P: Policy> ResourceLocator<PostgresqlDatabase<D>> for DefaultLocator<P> {
type Prerequisites = ();
fn locate(
resource: &PostgresqlDatabase<D>,
) -> (
<PostgresqlDatabase<D> as Resource>::Artifact,
Self::Prerequisites,
) {
let ((user_name, _), ()) = Self::locate(&UserForDomain(&resource.0));
(
(
DatabaseNameArtifact(user_name.0.clone()),
PathArtifact::from(P::path_for_data(format!("{}.sql", user_name.0))),
),
(),
)
}
}
impl<P, POLICY> ResourceLocator<WordpressPlugin<P>> for DefaultLocator<POLICY> {
type Prerequisites = ();
fn locate(
_resource: &WordpressPlugin<P>,
) -> (
<WordpressPlugin<P> as Resource>::Artifact,
Self::Prerequisites,
) {
((), ())
}
}
impl<P, POLICY> ResourceLocator<WordpressTranslation<P>> for DefaultLocator<POLICY> {
type Prerequisites = ();
fn locate(
_resource: &WordpressTranslation<P>,
) -> (
<WordpressTranslation<P> as Resource>::Artifact,
Self::Prerequisites,
) {
((), ())
}
}
impl<D, P> ResourceLocator<Cron<D>> for DefaultLocator<P> {
type Prerequisites = ();
fn locate(_resource: &Cron<D>) -> (<Cron<D> as Resource>::Artifact, Self::Prerequisites) {
((), ())
}
}