edgehog_device_runtime_containers/store/
mod.rs1use std::ops::Not;
22
23use edgehog_store::db::{self, HandleError};
24
25use crate::requests::{container::RestartPolicyError, BindingError};
26
27mod container;
28mod deployment;
29mod device_mapping;
30mod image;
31mod network;
32mod volume;
33
34type Result<T> = std::result::Result<T, StoreError>;
35
36#[derive(Debug, thiserror::Error, displaydoc::Display)]
38pub enum StoreError {
39 ParseKeyValue {
41 ctx: &'static str,
43 value: String,
45 },
46 PortBinding(#[from] BindingError),
48 RestartPolicy(#[from] RestartPolicyError),
50 Handle(#[from] HandleError),
52 Conversion {
54 ctx: String,
56 },
57}
58
59#[derive(Debug, Clone)]
63pub struct StateStore {
64 handle: db::Handle,
65}
66
67impl StateStore {
68 pub fn new(handle: db::Handle) -> Self {
70 Self { handle }
71 }
72}
73
74#[allow(dead_code)]
75fn split_key_value(value: &str) -> Option<(&str, Option<&str>)> {
76 value.split_once('=').and_then(|(k, v)| {
77 if k.is_empty() {
78 return None;
79 }
80
81 let v = v.is_empty().not().then_some(v);
82
83 Some((k, v))
84 })
85}
86
87#[cfg(test)]
88mod tests {
89 use pretty_assertions::assert_eq;
90 use tempfile::TempDir;
91 use uuid::Uuid;
92
93 use crate::requests::{
94 container::CreateContainer, deployment::CreateDeployment, image::CreateImage,
95 network::CreateNetwork, volume::CreateVolume, ReqUuid, VecReqUuid,
96 };
97
98 use super::*;
99
100 #[test]
101 fn should_parse_key_value() {
102 let cases = [
103 ("device=tmpfs", ("device", Some("tmpfs"))),
104 ("o=size=100m,uid=1000", ("o", Some("size=100m,uid=1000"))),
105 ("type=tmpfs", ("type", Some("tmpfs"))),
106 ];
107
108 for (case, exp) in cases {
109 let res = split_key_value(case).unwrap();
110
111 assert_eq!(res, exp);
112 }
113 }
114
115 #[tokio::test]
116 async fn should_create_missing() {
117 let tmp = TempDir::with_prefix("create_full_deployment").unwrap();
118 let db_file = tmp.path().join("state.db");
119 let db_file = db_file.to_str().unwrap();
120
121 let handle = db::Handle::open(db_file).await.unwrap();
122 let store = StateStore::new(handle);
123
124 let image_id = Uuid::new_v4();
125 let volume_id = ReqUuid(Uuid::new_v4());
126 let network_id = ReqUuid(Uuid::new_v4());
127 let device_mapping_id = ReqUuid(Uuid::new_v4());
128 let container_id = ReqUuid(Uuid::new_v4());
129 let deployment_id = ReqUuid(Uuid::new_v4());
130
131 let deployment = CreateDeployment {
132 id: deployment_id,
133 containers: VecReqUuid(vec![container_id]),
134 };
135 store.create_deployment(deployment).await.unwrap();
136
137 let container = CreateContainer {
138 id: container_id,
139 deployment_id: ReqUuid(image_id),
140 image_id: ReqUuid(image_id),
141 network_ids: VecReqUuid(vec![network_id]),
142 volume_ids: VecReqUuid(vec![volume_id]),
143 device_mapping_ids: VecReqUuid(vec![device_mapping_id]),
144 hostname: "database".to_string(),
145 restart_policy: "unless-stopped".to_string(),
146 env: ["POSTGRES_USER=user", "POSTGRES_PASSWORD=password"]
147 .map(str::to_string)
148 .to_vec(),
149 binds: vec!["/var/lib/postgres".to_string()],
150 network_mode: "bridge".to_string(),
151 port_bindings: vec!["5432:5432".to_string()],
152 extra_hosts: vec!["host.docker.internal:host-gateway".to_string()],
153 cap_add: vec!["CAP_CHOWN".to_string()],
154 cap_drop: vec!["CAP_KILL".to_string()],
155 cpu_period: 1000,
156 cpu_quota: 100,
157 cpu_realtime_period: 1000,
158 cpu_realtime_runtime: 100,
159 memory: 4096,
160 memory_reservation: 1024,
161 memory_swap: 8192,
162 memory_swappiness: 50,
163 volume_driver: "local".to_string().into(),
164 storage_opt: vec!["size=1024k".to_string()],
165 read_only_rootfs: true,
166 tmpfs: vec!["/run=rw,noexec,nosuid,size=65536k".to_string()],
167 privileged: false,
168 };
169 store.create_container(Box::new(container)).await.unwrap();
170
171 let network = CreateNetwork {
172 id: network_id,
173 deployment_id,
174 driver: "bridge".to_string(),
175 internal: true,
176 enable_ipv6: false,
177 options: vec!["isolate=true".to_string()],
178 };
179 store.create_network(network).await.unwrap();
180
181 let volume = CreateVolume {
182 id: volume_id,
183 deployment_id,
184 driver: "local".to_string(),
185 options: ["device=tmpfs", "o=size=100m,uid=1000", "type=tmpfs"]
186 .map(str::to_string)
187 .to_vec(),
188 };
189 store.create_volume(volume).await.unwrap();
190
191 let image = CreateImage {
192 id: ReqUuid(image_id),
193 deployment_id,
194 reference: "postgres:15".to_string(),
195 registry_auth: String::new(),
196 };
197 store.create_image(image).await.unwrap();
198 }
199}