Skip to main content

ignition_core/deploy/
mod.rs

1//! Offline render of declared gateway trees (Phase 21).
2//!
3//! Invariants every `deploy` module keeps:
4//! - **Pure and offline.** No network, no profile, no [`crate::session::Session`].
5//!   Functions take bytes (or an injected file set) and return models.
6//! - **Deterministic.** Ordered collections only, no clock reads, no
7//!   randomness, no dependence on directory iteration order. The same
8//!   input yields the same output or the same error.
9//! - **Core never prints.** Findings ride [`crate::error::CoreError`]; the
10//!   binary renders them.
11
12pub mod apply;
13pub mod exclude;
14pub mod layout;
15pub mod ledger;
16pub mod lint;
17pub mod manifest;
18pub mod native_tags;
19pub mod node;
20#[cfg(any(windows, test))]
21pub(crate) mod owner_acl;
22pub mod pair;
23pub mod plan;
24pub mod project_artifact;
25pub mod project_run;
26pub mod project_tags;
27pub mod projects;
28pub mod pull;
29pub mod render;
30pub mod rules;
31pub mod tag_apply;
32pub mod tag_canon;
33pub mod tag_converge;
34pub mod tag_delete;
35pub mod tag_groups;
36pub mod tag_plan;
37pub mod tag_record;
38pub mod target;
39pub mod transport;
40pub mod tree;
41pub mod uuid;
42pub mod verify;
43pub mod yamlio;
44
45/// Why `name` is not a safe single path component, or `None` when it is.
46///
47/// Used before any path is built from a declared name (`target.gateway`,
48/// `target.mode`, tag providers, type halves), so a hostile value cannot
49/// walk out of its directory (T-21-12). Refuses: empty, `.`, `..`, any `/`,
50/// `\` or NUL, and any dot-leading name.
51pub fn component_problem(name: &str) -> Option<&'static str> {
52    if name.is_empty() {
53        Some("must not be empty")
54    } else if name == "." || name == ".." {
55        Some("must not be `.` or `..`")
56    } else if name.contains('/') || name.contains('\\') {
57        Some("must not contain a path separator")
58    } else if name.contains('\0') {
59        Some("must not contain a NUL byte")
60    } else if name.starts_with('.') {
61        Some("must not start with `.`")
62    } else {
63        None
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::component_problem;
70
71    #[test]
72    fn safe_names_pass() {
73        for ok in ["prod", "gw-1", "my_gateway", "a.b", "Ignition 8"] {
74            assert_eq!(component_problem(ok), None, "{ok}");
75        }
76    }
77
78    #[test]
79    fn unsafe_names_are_refused() {
80        for bad in [
81            "", ".", "..", "a/b", "a\\b", "a\0b", ".hidden", "../x", "/abs",
82        ] {
83            assert!(component_problem(bad).is_some(), "{bad:?}");
84        }
85    }
86}