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
21pub 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 assert_eq!(report.checks.len(), 8);
177 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 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}