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
#![deny(unsafe_code)]
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
#![deny(clippy::arithmetic_side_effects)]
#![deny(clippy::dbg_macro, clippy::print_stdout, clippy::print_stderr)]
#![deny(clippy::todo)]
#![deny(clippy::unreachable)]
#![deny(clippy::allow_attributes_without_reason)]
#![allow(dead_code, reason = "remove once the time stuff is complete")]

mod config;
mod process;
mod service;

use std::fmt::Debug;

use futures_time::future::FutureExt;
use tracing_subscriber::{
  layer::SubscriberExt, util::SubscriberInitExt, EnvFilter,
};

// TODO: configurable timeouts

#[tokio::main]
#[tracing::instrument]
async fn main() -> anyhow::Result<()> {
  let format_layer = tracing_subscriber::fmt::layer();
  let (filter_layer, filter_handle) =
    tracing_subscriber::reload::Layer::new(build_tracing_filter("info")?);
  tracing_subscriber::registry()
    .with(filter_layer)
    .with(format_layer)
    .try_init()?;

  let manager = config::Manager::new().await?; // NITPICK: handle this more appropriately
  let config = manager.values().await;

  let log_level = config.log_level.to_string();
  filter_handle.modify(move |filter| {
    #[allow(clippy::unwrap_used, reason = "it should panic")]
    let new_filter = build_tracing_filter(log_level.as_str()).unwrap();
    *filter = new_filter;
  })?;

  let id = config.cloud.id.clone();
  tracing::info!("Starting {id}");

  let services = service::Container::new(config.clone());
  let processes = process::Container::new(manager.clone(), services.clone());

  services
    .db()
    .migrate()
    .timeout(futures_time::time::Duration::from_millis(60_000))
    .await??;

  processes.startup().await?;

  if let Err(error) = tokio::signal::ctrl_c().await {
    tracing::error!("Failed waiting for ctrlc signal {}", error);
  };
  if let Err(error) = processes
    .shutdown()
    .timeout(futures_time::time::Duration::from_millis(60_000))
    .await
  {
    tracing::error!("Timed out shutting down processes {}", error);
  }

  Ok(())
}

fn build_tracing_filter(level: &str) -> anyhow::Result<EnvFilter> {
  Ok(
    tracing_subscriber::EnvFilter::builder()
      .with_default_directive(tracing::level_filters::LevelFilter::WARN.into())
      .with_env_var("PIDGEON_LOG")
      .from_env()?
      .add_directive(format!("pidgeon={level}").parse()?),
  )
}