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];
18pub const GIT_IDENTITY_NAME: &str = "heddle ci";
20pub const GIT_IDENTITY_EMAIL: &str = "ci@heddle.invalid";
22
23#[derive(Debug, Clone)]
25pub struct HermeticEnv {
26 git_hermetic: bool,
27 host: BTreeMap<String, String>,
28}
29
30impl HermeticEnv {
31 #[must_use]
33 pub fn new() -> Self {
34 let host = BASE_ALLOWLIST
35 .iter()
36 .filter_map(|name| {
37 std::env::var(name)
38 .ok()
39 .map(|value| ((*name).to_string(), value))
40 })
41 .collect();
42 Self {
43 git_hermetic: true,
44 host,
45 }
46 }
47
48 #[must_use]
50 pub fn with_host(host: BTreeMap<String, String>) -> Self {
51 Self {
52 git_hermetic: true,
53 host,
54 }
55 }
56
57 #[must_use]
59 pub fn git_hermetic(mut self, enabled: bool) -> Self {
60 self.git_hermetic = enabled;
61 self
62 }
63
64 #[must_use]
66 pub fn build(
67 &self,
68 check: &BTreeMap<String, String>,
69 services: &BTreeMap<String, String>,
70 caches: &BTreeMap<String, String>,
71 ) -> BTreeMap<String, String> {
72 let mut output = self.host.clone();
73 if self.git_hermetic {
74 output.insert("GIT_CONFIG_GLOBAL".into(), "/dev/null".into());
75 output.insert("GIT_CONFIG_SYSTEM".into(), "/dev/null".into());
76 output.insert("GIT_AUTHOR_NAME".into(), GIT_IDENTITY_NAME.into());
77 output.insert("GIT_AUTHOR_EMAIL".into(), GIT_IDENTITY_EMAIL.into());
78 output.insert("GIT_COMMITTER_NAME".into(), GIT_IDENTITY_NAME.into());
79 output.insert("GIT_COMMITTER_EMAIL".into(), GIT_IDENTITY_EMAIL.into());
80 }
81 for source in [services, caches, check] {
82 output.extend(
83 source
84 .iter()
85 .map(|(key, value)| (key.clone(), value.clone())),
86 );
87 }
88 output
89 }
90}
91
92impl Default for HermeticEnv {
93 fn default() -> Self {
94 Self::new()
95 }
96}