kcode_k1_daemon_code_services/
lib.rs1#![doc = include_str!("../Documentation.md")]
2
3use kcode_k1_groups::K1Groups;
4use kcode_k1_objects::K1Objects;
5use kcode_k1_peering::K1Peering;
6use kcode_k1_rust_code_ktool_service::RustCodeKtoolService;
7use kcode_k1_rust_coding::{RustCodingConfig, RustCodingConfigValues};
8use kcode_k1_rust_projection::K1RustProjection;
9use kcode_k1_txn_ordering::K1TxnOrdering;
10use kcode_k1_web_code_ktool_service::{K1WebCodeKtoolService, ServiceConfig, ServiceRevisions};
11use kcode_k1_web_podman::{WebPodman, WebPodmanConfig};
12use kcode_k1_web_projection::K1WebProjection;
13use std::ffi::OsString;
14use std::fmt::Display;
15use std::fs;
16use std::path::{Path, PathBuf};
17use std::str::FromStr;
18use std::sync::Arc;
19use std::time::Duration;
20
21const PODMAN_ENV: &str = "K1_PODMAN_EXECUTABLE";
22const RUST_SCHEMA_ENV: &str = "K1_RUST_CODE_SCHEMA_REVISION";
23const RUST_TOOLCHAIN_POLICY_ENV: &str = "K1_RUST_CODE_TOOLCHAIN_POLICY_REVISION";
24const RUST_IMAGE_ENV: &str = "K1_RUST_CODE_IMAGE";
25const RUST_CHECK_POLICY_ENV: &str = "K1_RUST_CODE_CHECK_POLICY_REVISION";
26const RUST_TARGET_ENV: &str = "K1_RUST_TARGET_TRIPLE";
27const RUST_COMMAND_POLICY_ENV: &str = "K1_RUST_CODE_COMMAND_POLICY_REVISION";
28const WEB_IMAGE_ENV: &str = "K1_WEB_CODE_IMAGE";
29const WEB_CHECKER_ENV: &str = "K1_WEB_CHECKER_EXECUTABLE";
30const WEB_CHROMIUM_ENV: &str = "K1_WEB_CHROMIUM";
31const WEB_CHROMIUM_VERSION_ENV: &str = "K1_WEB_CHROMIUM_VERSION";
32const WEB_CPU_ENV: &str = "K1_WEB_CPU_MILLIS";
33const WEB_MEMORY_ENV: &str = "K1_WEB_MEMORY_BYTES";
34const WEB_PIDS_ENV: &str = "K1_WEB_PIDS_LIMIT";
35const WEB_TMPFS_ENV: &str = "K1_WEB_TMPFS_BYTES";
36const WEB_SHM_ENV: &str = "K1_WEB_SHM_BYTES";
37const WEB_CHECKER_TIMEOUT_ENV: &str = "K1_WEB_CHECKER_TIMEOUT_MS";
38const WEB_WALL_TIMEOUT_ENV: &str = "K1_WEB_WALL_TIMEOUT_MS";
39const WEB_BOOT_ENV: &str = "K1_WEB_CODE_BOOT_REVISION";
40const WEB_SCHEMA_ENV: &str = "K1_WEB_CODE_SCHEMA_REVISION";
41const WEB_ROUTE_ENV: &str = "K1_WEB_CODE_ROUTE_REVISION";
42const WEB_HARNESS_ENV: &str = "K1_WEB_CODE_HARNESS_REVISION";
43const WEB_CHECK_POLICY_ENV: &str = "K1_WEB_CODE_CHECK_POLICY_REVISION";
44
45pub struct CodeServices {
46 rust: Arc<RustCodeKtoolService>,
47 web: K1WebCodeKtoolService,
48}
49
50impl CodeServices {
51 #[allow(clippy::too_many_arguments)]
52 pub fn open(
53 state_root: &Path,
54 ordering: Arc<K1TxnOrdering>,
55 peering: Arc<K1Peering>,
56 groups: Arc<K1Groups>,
57 objects: Arc<K1Objects>,
58 web_projection: Arc<K1WebProjection>,
59 ) -> Result<Self, String> {
60 let paths = CodePaths::open(state_root)?;
61 let podman = required_os(PODMAN_ENV)?;
62 let rust_config = rust_config(podman.clone())?;
63 let web_config = web_config(podman)?;
64 WebPodman::new(web_config.clone())
65 .map_err(|error| format!("Web Podman configuration: {error}"))?;
66
67 let rust_projection =
68 K1RustProjection::open(paths.rust_projection, paths.rust_control, ordering, peering)
69 .map(Arc::new)
70 .map_err(|error| format!("open Rust projection: {error}"))?;
71 let rust = Arc::new(RustCodeKtoolService::new(
72 paths.rust_cache,
73 rust_config,
74 rust_projection,
75 Arc::clone(&objects),
76 Arc::clone(&groups),
77 ));
78 let web = K1WebCodeKtoolService::new(
79 ServiceConfig::new(
80 paths.web_cache,
81 paths.web_projection,
82 web_revisions()?,
83 web_config,
84 ),
85 groups,
86 objects,
87 web_projection,
88 );
89 Ok(Self { rust, web })
90 }
91
92 pub fn into_parts(self) -> (Arc<RustCodeKtoolService>, K1WebCodeKtoolService) {
93 (self.rust, self.web)
94 }
95}
96
97struct CodePaths {
98 rust_projection: PathBuf,
99 rust_control: PathBuf,
100 rust_cache: PathBuf,
101 web_projection: PathBuf,
102 web_cache: PathBuf,
103}
104
105impl CodePaths {
106 fn open(state_root: &Path) -> Result<Self, String> {
107 let rust = state_root.join("rust");
108 let web = state_root.join("web");
109 ensure_directory(&rust)?;
110 ensure_directory(&web)?;
111 let value = Self {
112 rust_projection: rust.join("projection"),
113 rust_control: rust.join("control"),
114 rust_cache: rust.join("code-cache"),
115 web_projection: web.join("projection"),
116 web_cache: web.join("code-cache"),
117 };
118 for path in [
119 &value.rust_projection,
120 &value.rust_control,
121 &value.rust_cache,
122 &value.web_projection,
123 &value.web_cache,
124 ] {
125 ensure_directory(path)?;
126 }
127 Ok(value)
128 }
129}
130
131fn rust_config(podman_program: OsString) -> Result<RustCodingConfig, String> {
132 RustCodingConfig::new(RustCodingConfigValues {
133 schema_id: required_string(RUST_SCHEMA_ENV)?,
134 toolchain_policy: required_string(RUST_TOOLCHAIN_POLICY_ENV)?,
135 image: required_string(RUST_IMAGE_ENV)?,
136 rust_toolchain: "1.97".into(),
137 check_policy: required_string(RUST_CHECK_POLICY_ENV)?,
138 target_triple: required_string(RUST_TARGET_ENV)?,
139 command_policy: required_string(RUST_COMMAND_POLICY_ENV)?,
140 podman_program,
141 })
142 .map_err(|error| format!("Rust coding configuration: {error}"))
143}
144
145fn web_config(podman: OsString) -> Result<WebPodmanConfig, String> {
146 let checker_timeout = Duration::from_millis(positive(WEB_CHECKER_TIMEOUT_ENV)?);
147 let wall_timeout = Duration::from_millis(positive(WEB_WALL_TIMEOUT_ENV)?);
148 if wall_timeout <= checker_timeout {
149 return Err(format!(
150 "{WEB_WALL_TIMEOUT_ENV} must exceed {WEB_CHECKER_TIMEOUT_ENV}"
151 ));
152 }
153 Ok(WebPodmanConfig {
154 podman: PathBuf::from(podman),
155 image: required_string(WEB_IMAGE_ENV)?,
156 checker: PathBuf::from(required_os(WEB_CHECKER_ENV)?),
157 chromium: PathBuf::from(required_os(WEB_CHROMIUM_ENV)?),
158 chromium_version: required_string(WEB_CHROMIUM_VERSION_ENV)?,
159 cpu_millis: positive(WEB_CPU_ENV)?,
160 memory_bytes: positive(WEB_MEMORY_ENV)?,
161 pids_limit: positive(WEB_PIDS_ENV)?,
162 tmpfs_bytes: positive(WEB_TMPFS_ENV)?,
163 shm_bytes: positive(WEB_SHM_ENV)?,
164 checker_timeout,
165 wall_timeout,
166 })
167}
168
169fn web_revisions() -> Result<ServiceRevisions, String> {
170 Ok(ServiceRevisions {
171 boot: required_string(WEB_BOOT_ENV)?,
172 schema: required_string(WEB_SCHEMA_ENV)?,
173 route: required_string(WEB_ROUTE_ENV)?,
174 harness: required_string(WEB_HARNESS_ENV)?,
175 check_policy: required_string(WEB_CHECK_POLICY_ENV)?,
176 })
177}
178
179fn required_os(name: &str) -> Result<OsString, String> {
180 let value = std::env::var_os(name).ok_or_else(|| format!("{name} is required"))?;
181 if value.is_empty() {
182 Err(format!("{name} must be nonempty"))
183 } else {
184 Ok(value)
185 }
186}
187
188fn required_string(name: &str) -> Result<String, String> {
189 required_os(name)?
190 .into_string()
191 .map_err(|_| format!("{name} must be UTF-8"))
192}
193
194fn positive<T>(name: &str) -> Result<T, String>
195where
196 T: FromStr + Default + PartialEq,
197 T::Err: Display,
198{
199 let value = required_string(name)?
200 .parse::<T>()
201 .map_err(|error| format!("{name} is invalid: {error}"))?;
202 if value == T::default() {
203 Err(format!("{name} must be nonzero"))
204 } else {
205 Ok(value)
206 }
207}
208
209fn ensure_directory(path: &Path) -> Result<(), String> {
210 match fs::symlink_metadata(path) {
211 Ok(metadata) if metadata.is_dir() && !metadata.file_type().is_symlink() => Ok(()),
212 Ok(_) => Err(format!(
213 "code-service path is not an ordinary directory: {}",
214 path.display()
215 )),
216 Err(error) if error.kind() == std::io::ErrorKind::NotFound => fs::create_dir(path)
217 .map_err(|error| format!("create code-service directory {}: {error}", path.display())),
218 Err(error) => Err(format!(
219 "inspect code-service directory {}: {error}",
220 path.display()
221 )),
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn fixed_paths_are_beneath_state_root() {
231 let root = tempfile::tempdir().unwrap();
232 fs::create_dir(root.path().join("web")).unwrap();
233 fs::create_dir(root.path().join("web/projection")).unwrap();
234 let paths = CodePaths::open(root.path()).unwrap();
235 assert_eq!(paths.rust_projection, root.path().join("rust/projection"));
236 assert_eq!(paths.rust_control, root.path().join("rust/control"));
237 assert_eq!(paths.rust_cache, root.path().join("rust/code-cache"));
238 assert_eq!(paths.web_projection, root.path().join("web/projection"));
239 assert_eq!(paths.web_cache, root.path().join("web/code-cache"));
240 }
241}