1use std::collections::BTreeMap;
5
6pub const BASE_ALLOWLIST: &[&str] = &[
8 "PATH",
9 "HOME",
10 "USER",
11 "SHELL",
12 "TERM",
13 "LANG",
14 "LC_ALL",
15 "CARGO_HOME",
16 "RUSTUP_HOME",
17 "TMPDIR",
18 "TEMP",
19];
20pub const GIT_IDENTITY_NAME: &str = "heddle ci";
22pub const GIT_IDENTITY_EMAIL: &str = "ci@heddle.invalid";
24
25#[derive(Debug, Clone)]
27pub struct HermeticEnv {
28 git_hermetic: bool,
29 host: BTreeMap<String, String>,
30}
31
32impl HermeticEnv {
33 #[must_use]
35 pub fn new() -> Self {
36 let host = BASE_ALLOWLIST
37 .iter()
38 .filter_map(|name| {
39 std::env::var(name)
40 .ok()
41 .map(|value| ((*name).to_string(), value))
42 })
43 .collect();
44 Self {
45 git_hermetic: true,
46 host,
47 }
48 }
49
50 #[must_use]
52 pub fn with_host(host: BTreeMap<String, String>) -> Self {
53 Self {
54 git_hermetic: true,
55 host,
56 }
57 }
58
59 #[must_use]
61 pub fn git_hermetic(mut self, enabled: bool) -> Self {
62 self.git_hermetic = enabled;
63 self
64 }
65
66 #[must_use]
68 pub fn build(
69 &self,
70 check: &BTreeMap<String, String>,
71 services: &BTreeMap<String, String>,
72 caches: &BTreeMap<String, String>,
73 ) -> BTreeMap<String, String> {
74 let mut output = self.host.clone();
75 if self.git_hermetic {
76 output.insert("GIT_CONFIG_GLOBAL".into(), "/dev/null".into());
77 output.insert("GIT_CONFIG_SYSTEM".into(), "/dev/null".into());
78 output.insert("GIT_AUTHOR_NAME".into(), GIT_IDENTITY_NAME.into());
79 output.insert("GIT_AUTHOR_EMAIL".into(), GIT_IDENTITY_EMAIL.into());
80 output.insert("GIT_COMMITTER_NAME".into(), GIT_IDENTITY_NAME.into());
81 output.insert("GIT_COMMITTER_EMAIL".into(), GIT_IDENTITY_EMAIL.into());
82 }
83 for source in [services, caches, check] {
84 output.extend(
85 source
86 .iter()
87 .map(|(key, value)| (key.clone(), value.clone())),
88 );
89 }
90 output
91 }
92}
93
94impl Default for HermeticEnv {
95 fn default() -> Self {
96 Self::new()
97 }
98}