gardesk/garcard / 79b89e8

Browse files

Expose authority health in status surface

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
79b89e8884c5f2decd2fd5c67d2cbcba76896bf8
Parents
9d3a589
Tree
d29c381

3 changed files

StatusFile+-
M garcard-ipc/src/lib.rs 8 0
M garcard/src/daemon.rs 22 1
M garcard/src/state.rs 14 0
garcard-ipc/src/lib.rsmodified
@@ -74,6 +74,14 @@ pub struct StatusData {
7474
     pub protocol_version: u32,
7575
     pub socket_path: String,
7676
     pub agent_backend: String,
77
+    #[serde(skip_serializing_if = "Option::is_none")]
78
+    pub authority_connected: Option<bool>,
79
+    #[serde(skip_serializing_if = "Option::is_none")]
80
+    pub authority_error: Option<String>,
81
+    #[serde(skip_serializing_if = "Option::is_none")]
82
+    pub subject_kind: Option<String>,
83
+    #[serde(skip_serializing_if = "Option::is_none")]
84
+    pub temporary_authorization_count: Option<usize>,
7785
 }
7886
 
7987
 /// Version handshake payload.
garcard/src/daemon.rsmodified
@@ -287,7 +287,15 @@ fn dispatch(
287287
 ) -> Response {
288288
     match command {
289289
         Command::Ping => Response::ok_with_data(json!({ "pong": true })),
290
-        Command::Status => Response::ok_with_data(state.status()),
290
+        Command::Status => {
291
+            let mut status = state.status();
292
+            let diagnostics = collect_authority_diagnostics();
293
+            status.authority_connected = Some(diagnostics.authority_connected);
294
+            status.authority_error = diagnostics.authority_error.clone();
295
+            status.subject_kind = Some(diagnostics.subject.kind.clone());
296
+            status.temporary_authorization_count = diagnostics.temporary_authorization_count;
297
+            Response::ok_with_data(status)
298
+        }
291299
         Command::Diagnose => {
292300
             let diagnostics = collect_authority_diagnostics();
293301
             let auth_summary = state.auth_summary();
@@ -415,6 +423,19 @@ mod tests {
415423
         assert_eq!(data.get("pong").and_then(|v| v.as_bool()), Some(true));
416424
     }
417425
 
426
+    #[test]
427
+    fn dispatch_status_includes_health_surface_fields() {
428
+        let state = fake_state();
429
+        let (shutdown_tx, _shutdown_rx) = mpsc::unbounded_channel();
430
+
431
+        let response = dispatch(Command::Status, &state, &shutdown_tx);
432
+        assert!(response.success);
433
+
434
+        let data = response.data.expect("data");
435
+        assert!(data.get("authority_connected").is_some());
436
+        assert!(data.get("subject_kind").is_some());
437
+    }
438
+
418439
     #[test]
419440
     fn dispatch_quit_signals_shutdown() {
420441
         let state = fake_state();
garcard/src/state.rsmodified
@@ -330,6 +330,10 @@ impl RuntimeState {
330330
             protocol_version: PROTOCOL_VERSION,
331331
             socket_path: self.socket_path.clone(),
332332
             agent_backend: self.backend_name.to_string(),
333
+            authority_connected: None,
334
+            authority_error: None,
335
+            subject_kind: None,
336
+            temporary_authorization_count: None,
333337
         }
334338
     }
335339
 
@@ -381,6 +385,16 @@ mod tests {
381385
         assert_eq!(summary.queued_requests, 3);
382386
     }
383387
 
388
+    #[test]
389
+    fn runtime_status_initializes_optional_health_fields() {
390
+        let runtime = RuntimeState::new("/tmp/garcard-test.sock".to_string(), "test-backend");
391
+        let status = runtime.status();
392
+        assert!(status.authority_connected.is_none());
393
+        assert!(status.authority_error.is_none());
394
+        assert!(status.subject_kind.is_none());
395
+        assert!(status.temporary_authorization_count.is_none());
396
+    }
397
+
384398
     #[test]
385399
     fn auth_state_records_last_decision_context() {
386400
         let state = AuthState::default();