1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use anyhow::Result;
use futures_util::TryFutureExt;
use sqlx::migrate::Migrator;
use sqlx::postgres::{PgConnectOptions, PgPoolOptions};
use sqlx::PgPool;
use std::env;
use std::str::FromStr;
use std::sync::Arc;
use tokio::runtime::Runtime;
use tokio::signal::ctrl_c;
use tracing::{debug, info, Instrument};

use acme::DatabasePersist;
use cert::CertManager;
use dns::{DatabaseAuthority, Dns};
use facade::DatabaseFacade;

mod acme;
pub mod api;
mod cert;
mod config;
mod dns;
pub mod facade;
pub mod util;

static MIGRATOR: Migrator = sqlx::migrate!("migrations/postgres");

#[tracing::instrument]
pub fn run() -> Result<()> {
    let config_path = env::args().nth(1);
    let config = config::load_config(config_path)?;

    let runtime = Arc::new(Runtime::new()?);
    debug!("Created runtime");

    // Async closure cannot be move, if runtime gets moved into it
    // it gets dropped inside an async call
    let fut = async {
        debug!("Running in runtime");

        let pool = setup_database(&config.general.db).await?;
        let facade = DatabaseFacade::from(pool.clone());
        let authority =
            DatabaseAuthority::new(facade.clone(), &config.general.name, config.records);
        let dns = Dns::new(&config.general.dns, authority);

        let api = &config.api;
        let api = api::new(
            api.http.clone(),
            api.https.clone(),
            api.prom.clone(),
            facade.clone(),
        );

        let persist = DatabasePersist::new(pool, &runtime);
        let cert_manager = CertManager::new(facade, persist, config.general.acme, &runtime)
            .and_then(CertManager::spawn);

        info!("Starting API Cert Manager and DNS");
        tokio::select! {
            res = api => res,
            res = cert_manager => res,
            res = dns.spawn() => res,
            res = ctrl_c() => {
                res?;
                info!("Ctrl C pressed");
                Ok(())
            }
        }
    };

    runtime.block_on(fut.in_current_span())
}

#[tracing::instrument(skip(db))]
async fn setup_database(db: &str) -> Result<PgPool, sqlx::Error> {
    debug!("Starting DB Setup");
    let options = PgConnectOptions::from_str(db)?;
    let pool = PgPoolOptions::new()
        .max_connections(5)
        .connect_with(options)
        .await?;
    debug!(?pool, "Created DB pool");

    MIGRATOR.run(&pool).await?;
    info!("Ran migration");
    Ok(pool)
}

#[cfg(test)]
mod tests {
    use testcontainers::*;

    use super::setup_database;

    #[cfg(not(feature = "disable-docker"))]
    #[tokio::test]
    async fn test_setup_database() {
        let docker = clients::Cli::default();
        let node = docker.run(images::postgres::Postgres::default());

        let connection_string = &format!(
            "postgres://postgres:postgres@localhost:{}/postgres",
            node.get_host_port(5432).unwrap()
        );

        let pool = setup_database(connection_string).await.unwrap();

        let actual: (i64,) = sqlx::query_as("SELECT $1")
            .bind(150_i64)
            .fetch_one(&pool)
            .await
            .unwrap();

        assert_eq!(150_i64, actual.0)
    }
}