Skip to main content

cfgd_core/state/
modules.rs

1use rusqlite::params;
2
3use super::StateStore;
4use super::types::{ModuleFileRecord, ModuleStateRecord};
5use crate::errors::{Result, StateError};
6
7impl StateStore {
8    /// Insert or update module state.
9    pub fn upsert_module_state(
10        &self,
11        module_name: &str,
12        last_applied: Option<i64>,
13        packages_hash: &str,
14        files_hash: &str,
15        git_sources: Option<&str>,
16        status: &str,
17    ) -> Result<()> {
18        let now = crate::utc_now_iso8601();
19        self.conn
20            .execute(
21                "INSERT INTO module_state (module_name, installed_at, last_applied, packages_hash, files_hash, git_sources, status)
22                 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
23                 ON CONFLICT(module_name) DO UPDATE SET
24                    last_applied = ?3,
25                    packages_hash = ?4,
26                    files_hash = ?5,
27                    git_sources = ?6,
28                    status = ?7",
29                params![module_name, now, last_applied, packages_hash, files_hash, git_sources, status],
30            )
31            ?;
32        Ok(())
33    }
34
35    /// Get all module states.
36    pub fn module_states(&self) -> Result<Vec<ModuleStateRecord>> {
37        let mut stmt = self
38            .conn
39            .prepare(
40                "SELECT module_name, installed_at, last_applied, packages_hash, files_hash, git_sources, status
41                 FROM module_state ORDER BY module_name",
42            )
43            ?;
44
45        let records = stmt
46            .query_map([], |row| {
47                Ok(ModuleStateRecord {
48                    module_name: row.get(0)?,
49                    installed_at: row.get(1)?,
50                    last_applied: row.get(2)?,
51                    packages_hash: row.get(3)?,
52                    files_hash: row.get(4)?,
53                    git_sources: row.get(5)?,
54                    status: row.get(6)?,
55                })
56            })?
57            .collect::<std::result::Result<Vec<_>, _>>()?;
58
59        Ok(records)
60    }
61
62    /// Get module state by name.
63    pub fn module_state_by_name(&self, module_name: &str) -> Result<Option<ModuleStateRecord>> {
64        let mut stmt = self
65            .conn
66            .prepare(
67                "SELECT module_name, installed_at, last_applied, packages_hash, files_hash, git_sources, status
68                 FROM module_state WHERE module_name = ?1",
69            )
70            ?;
71
72        let mut rows = stmt.query_map(params![module_name], |row| {
73            Ok(ModuleStateRecord {
74                module_name: row.get(0)?,
75                installed_at: row.get(1)?,
76                last_applied: row.get(2)?,
77                packages_hash: row.get(3)?,
78                files_hash: row.get(4)?,
79                git_sources: row.get(5)?,
80                status: row.get(6)?,
81            })
82        })?;
83
84        match rows.next() {
85            Some(Ok(record)) => Ok(Some(record)),
86            Some(Err(e)) => Err(StateError::Database(e.to_string()).into()),
87            None => Ok(None),
88        }
89    }
90
91    /// Remove module state by name.
92    pub fn remove_module_state(&self, module_name: &str) -> Result<()> {
93        self.conn.execute(
94            "DELETE FROM module_state WHERE module_name = ?1",
95            params![module_name],
96        )?;
97        Ok(())
98    }
99
100    /// Record a file deployed by a module.
101    pub fn upsert_module_file(
102        &self,
103        module_name: &str,
104        file_path: &str,
105        content_hash: &str,
106        strategy: &str,
107        apply_id: i64,
108    ) -> Result<()> {
109        self.conn.execute(
110            "INSERT INTO module_file_manifest (module_name, file_path, content_hash, strategy, last_applied)
111             VALUES (?1, ?2, ?3, ?4, ?5)
112             ON CONFLICT(module_name, file_path) DO UPDATE SET
113                content_hash = excluded.content_hash,
114                strategy = excluded.strategy,
115                last_applied = excluded.last_applied",
116            params![module_name, file_path, content_hash, strategy, apply_id],
117        )?;
118        Ok(())
119    }
120
121    /// Get all files deployed by a module.
122    pub fn module_deployed_files(&self, module_name: &str) -> Result<Vec<ModuleFileRecord>> {
123        let mut stmt = self.conn.prepare(
124            "SELECT module_name, file_path, content_hash, strategy, last_applied
125             FROM module_file_manifest WHERE module_name = ?1 ORDER BY file_path",
126        )?;
127
128        let records = stmt
129            .query_map(params![module_name], |row| {
130                Ok(ModuleFileRecord {
131                    module_name: row.get(0)?,
132                    file_path: row.get(1)?,
133                    content_hash: row.get(2)?,
134                    strategy: row.get(3)?,
135                    last_applied: row.get(4)?,
136                })
137            })?
138            .collect::<std::result::Result<Vec<_>, _>>()?;
139
140        Ok(records)
141    }
142
143    /// Delete all manifest entries for a module.
144    pub fn delete_module_files(&self, module_name: &str) -> Result<()> {
145        self.conn.execute(
146            "DELETE FROM module_file_manifest WHERE module_name = ?1",
147            params![module_name],
148        )?;
149        Ok(())
150    }
151}