mithril_aggregator/commands/
tools_command.rs

1use anyhow::Context;
2use clap::{Parser, Subcommand};
3use config::{ConfigBuilder, Map, Value, builder::DefaultState};
4use serde::{Deserialize, Serialize};
5use slog::{Logger, debug};
6use std::{collections::HashMap, path::PathBuf, sync::Arc};
7
8use mithril_common::StdResult;
9use mithril_doc::{Documenter, StructDoc};
10use mithril_persistence::sqlite::{SqliteCleaner, SqliteCleaningTask};
11
12use crate::{
13    ConfigurationSource, ExecutionEnvironment,
14    database::repository::{CertificateRepository, SignedEntityStore},
15    dependency_injection::DependenciesBuilder,
16    extract_all,
17    tools::CertificatesHashMigrator,
18};
19
20#[derive(Debug, Clone, Serialize, Deserialize, Documenter)]
21pub struct ToolsCommandConfiguration {
22    /// Directory to store aggregator databases
23    #[example = "`./mithril-aggregator/stores`"]
24    pub data_stores_directory: PathBuf,
25}
26
27impl ConfigurationSource for ToolsCommandConfiguration {
28    fn environment(&self) -> ExecutionEnvironment {
29        ExecutionEnvironment::Production
30    }
31
32    fn data_stores_directory(&self) -> PathBuf {
33        self.data_stores_directory.clone()
34    }
35}
36
37/// List of tools to upkeep the aggregator
38#[derive(Parser, Debug, Clone)]
39pub struct ToolsCommand {
40    /// commands
41    #[clap(subcommand)]
42    pub genesis_subcommand: ToolsSubCommand,
43}
44
45impl ToolsCommand {
46    pub async fn execute(
47        &self,
48        root_logger: Logger,
49        config_builder: ConfigBuilder<DefaultState>,
50    ) -> StdResult<()> {
51        self.genesis_subcommand.execute(root_logger, config_builder).await
52    }
53
54    pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
55        extract_all!(
56            command_path,
57            ToolsSubCommand,
58            RecomputeCertificatesHash = { RecomputeCertificatesHashCommand },
59        )
60    }
61}
62
63/// Tools subcommands.
64#[derive(Debug, Clone, Subcommand)]
65pub enum ToolsSubCommand {
66    /// Load all certificates in the database to recompute their hash and update all related
67    /// entities.
68    ///
69    /// Since it will modify the aggregator sqlite database it's strongly recommended to backup it
70    /// before running this command.
71    RecomputeCertificatesHash(RecomputeCertificatesHashCommand),
72}
73
74impl ToolsSubCommand {
75    pub async fn execute(
76        &self,
77        root_logger: Logger,
78        config_builder: ConfigBuilder<DefaultState>,
79    ) -> StdResult<()> {
80        match self {
81            Self::RecomputeCertificatesHash(cmd) => cmd.execute(root_logger, config_builder).await,
82        }
83    }
84}
85
86/// Recompute certificates hash command.
87#[derive(Parser, Debug, Clone)]
88pub struct RecomputeCertificatesHashCommand {}
89
90impl RecomputeCertificatesHashCommand {
91    pub async fn execute(
92        &self,
93        root_logger: Logger,
94        config_builder: ConfigBuilder<DefaultState>,
95    ) -> StdResult<()> {
96        let config: ToolsCommandConfiguration = config_builder
97            .build()
98            .with_context(|| "configuration build error")?
99            .try_deserialize()
100            .with_context(|| "configuration deserialize error")?;
101        debug!(root_logger, "RECOMPUTE CERTIFICATES HASH command"; "config" => format!("{config:?}"));
102        println!("Recomputing all certificate hash",);
103        let mut dependencies_builder =
104            DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone()));
105
106        let dependencies_container = dependencies_builder
107            .create_tools_command_container()
108            .await
109            .with_context(|| "Failed to create the tools command dependencies container")?;
110
111        let migrator = CertificatesHashMigrator::new(
112            CertificateRepository::new(dependencies_container.db_connection.clone()),
113            Arc::new(SignedEntityStore::new(
114                dependencies_container.db_connection.clone(),
115            )),
116            root_logger,
117        );
118
119        migrator
120            .migrate()
121            .await
122            .with_context(|| "recompute-certificates-hash: database migration error")?;
123
124        SqliteCleaner::new(&dependencies_container.db_connection)
125            .with_tasks(&[SqliteCleaningTask::Vacuum])
126            .run()
127            .with_context(|| "recompute-certificates-hash: database vacuum error")?;
128
129        Ok(())
130    }
131
132    pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
133        HashMap::from([(command_path, ToolsCommandConfiguration::extract())])
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use std::sync::Arc;
140
141    use mithril_common::temp_dir;
142
143    use crate::test_tools::TestLogger;
144
145    use super::*;
146
147    #[tokio::test]
148    async fn create_container_does_not_panic() {
149        let config = ToolsCommandConfiguration {
150            data_stores_directory: temp_dir!().join("stores"),
151        };
152        let mut dependencies_builder =
153            DependenciesBuilder::new(TestLogger::stdout(), Arc::new(config));
154
155        dependencies_builder
156            .create_tools_command_container()
157            .await
158            .expect("Expected container creation to succeed without panicking");
159    }
160}