Skip to main content

cargo_codesign/
status.rs

1use crate::config::{MacosAuth, SignConfig};
2
3#[derive(Debug)]
4pub struct CheckResult {
5    pub name: String,
6    pub passed: bool,
7    pub detail: String,
8}
9
10#[derive(Debug)]
11pub struct StatusReport {
12    pub checks: Vec<CheckResult>,
13}
14
15impl StatusReport {
16    pub fn all_passed(&self) -> bool {
17        self.checks.iter().all(|c| c.passed)
18    }
19}
20
21/// Run all status checks for the given config.
22pub fn check_status(config: &SignConfig) -> StatusReport {
23    let mut checks = Vec::new();
24
25    if let Some(macos) = &config.macos {
26        match macos.auth {
27            MacosAuth::ApiKey => {
28                check_env(&mut checks, macos.env.certificate.as_ref(), "certificate");
29                check_env(
30                    &mut checks,
31                    macos.env.certificate_password.as_ref(),
32                    "certificate-password",
33                );
34                check_env(
35                    &mut checks,
36                    macos.env.notarization_key.as_ref(),
37                    "notarization-key",
38                );
39                check_env(
40                    &mut checks,
41                    macos.env.notarization_key_id.as_ref(),
42                    "notarization-key-id",
43                );
44                check_env(
45                    &mut checks,
46                    macos.env.notarization_issuer.as_ref(),
47                    "notarization-issuer",
48                );
49            }
50            MacosAuth::AppleId => {
51                check_env(&mut checks, macos.env.apple_id.as_ref(), "apple-id");
52                check_env(&mut checks, macos.env.team_id.as_ref(), "team-id");
53                check_env(&mut checks, macos.env.app_password.as_ref(), "app-password");
54            }
55        }
56
57        check_tool(&mut checks, "codesign");
58        check_tool(&mut checks, "xcrun");
59        check_tool(&mut checks, "hdiutil");
60    }
61
62    if let Some(windows) = &config.windows {
63        check_env(&mut checks, windows.env.tenant_id.as_ref(), "tenant-id");
64        check_env(&mut checks, windows.env.client_id.as_ref(), "client-id");
65        check_env(
66            &mut checks,
67            windows.env.client_secret.as_ref(),
68            "client-secret",
69        );
70        check_env(&mut checks, windows.env.endpoint.as_ref(), "endpoint");
71        check_env(
72            &mut checks,
73            windows.env.account_name.as_ref(),
74            "account-name",
75        );
76        check_env(
77            &mut checks,
78            windows.env.cert_profile.as_ref(),
79            "cert-profile",
80        );
81    }
82
83    if let Some(linux) = &config.linux {
84        check_env(&mut checks, linux.env.key.as_ref(), "key");
85    }
86
87    if let Some(update) = &config.update {
88        check_env(&mut checks, update.env.signing_key.as_ref(), "signing-key");
89    }
90
91    StatusReport { checks }
92}
93
94fn check_env(checks: &mut Vec<CheckResult>, env_name: Option<&String>, field_name: &str) {
95    let Some(env_var) = env_name else {
96        checks.push(CheckResult {
97            name: format!("env:{field_name}"),
98            passed: false,
99            detail: format!("{field_name} not configured in sign.toml"),
100        });
101        return;
102    };
103
104    match std::env::var(env_var) {
105        Ok(val) if !val.is_empty() => {
106            checks.push(CheckResult {
107                name: format!("env:{env_var}"),
108                passed: true,
109                detail: "set".to_string(),
110            });
111        }
112        _ => {
113            checks.push(CheckResult {
114                name: format!("env:{env_var}"),
115                passed: false,
116                detail: format!("{env_var}: not set"),
117            });
118        }
119    }
120}
121
122fn check_tool(checks: &mut Vec<CheckResult>, tool: &str) {
123    let result = crate::subprocess::run("which", &[tool], false);
124    match result {
125        Ok(output) if output.success => {
126            checks.push(CheckResult {
127                name: format!("tool:{tool}"),
128                passed: true,
129                detail: output.stdout.trim().to_string(),
130            });
131        }
132        _ => {
133            checks.push(CheckResult {
134                name: format!("tool:{tool}"),
135                passed: false,
136                detail: format!("{tool}: not found in PATH"),
137            });
138        }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use crate::config::*;
146
147    #[test]
148    fn status_empty_config_passes() {
149        let config = SignConfig::default();
150        let report = check_status(&config);
151        assert!(report.checks.is_empty());
152        assert!(report.all_passed());
153    }
154
155    #[test]
156    fn status_checks_macos_api_key_env_vars() {
157        let config = SignConfig {
158            macos: Some(MacosConfig {
159                identity: None,
160                entitlements: None,
161                auth: MacosAuth::ApiKey,
162                env: MacosEnvConfig {
163                    certificate: Some("NONEXISTENT_TEST_VAR_1".to_string()),
164                    certificate_password: Some("NONEXISTENT_TEST_VAR_2".to_string()),
165                    notarization_key: Some("NONEXISTENT_TEST_VAR_3".to_string()),
166                    notarization_key_id: Some("NONEXISTENT_TEST_VAR_4".to_string()),
167                    notarization_issuer: Some("NONEXISTENT_TEST_VAR_5".to_string()),
168                    ..Default::default()
169                },
170                dmg: None,
171            }),
172            ..Default::default()
173        };
174        let report = check_status(&config);
175        // 5 env var checks + 3 tool checks (codesign, xcrun, hdiutil)
176        assert_eq!(report.checks.len(), 8);
177        // env vars should all fail (not set)
178        let env_checks: Vec<_> = report
179            .checks
180            .iter()
181            .filter(|c| c.name.starts_with("env:"))
182            .collect();
183        assert_eq!(env_checks.len(), 5);
184        assert!(env_checks.iter().all(|c| !c.passed));
185    }
186
187    #[test]
188    fn status_checks_macos_apple_id_env_vars() {
189        let config = SignConfig {
190            macos: Some(MacosConfig {
191                identity: None,
192                entitlements: None,
193                auth: MacosAuth::AppleId,
194                env: MacosEnvConfig {
195                    apple_id: Some("NONEXISTENT_TEST_VAR_A".to_string()),
196                    team_id: Some("NONEXISTENT_TEST_VAR_B".to_string()),
197                    app_password: Some("NONEXISTENT_TEST_VAR_C".to_string()),
198                    ..Default::default()
199                },
200                dmg: None,
201            }),
202            ..Default::default()
203        };
204        let report = check_status(&config);
205        // 3 env var checks + 3 tool checks
206        let env_checks: Vec<_> = report
207            .checks
208            .iter()
209            .filter(|c| c.name.starts_with("env:"))
210            .collect();
211        assert_eq!(env_checks.len(), 3);
212    }
213
214    #[test]
215    fn status_reports_set_env_var() {
216        std::env::set_var("CARGO_SIGN_TEST_KEY", "some_value");
217        let config = SignConfig {
218            update: Some(UpdateConfig {
219                public_key: None,
220                env: UpdateEnvConfig {
221                    signing_key: Some("CARGO_SIGN_TEST_KEY".to_string()),
222                },
223            }),
224            ..Default::default()
225        };
226        let report = check_status(&config);
227        assert_eq!(report.checks.len(), 1);
228        assert!(report.checks[0].passed);
229        std::env::remove_var("CARGO_SIGN_TEST_KEY");
230    }
231}