2 atomic
::{AtomicUsize, Ordering}
,
6 use anyhow
::{bail, Error}
;
8 use serde_json
::{json, Value}
;
9 use tokio
::signal
::unix
::{signal, SignalKind}
;
11 use proxmox_router
::cli
::format_and_print_result
;
13 use pbs_api_types
::percent_encoding
::percent_encode_component
;
15 use super::HttpClient
;
17 /// Display task log on console
19 /// This polls the task API and prints the log to the console. It also
20 /// catches interrupt signals, and sends an abort request to the task if the
21 /// user presses CTRL-C and `forward_interrupt` is true. Two interrupts cause an
22 /// immediate end of the loop. The task may still run in that case.
23 pub async
fn display_task_log(
27 forward_interrupt
: bool
,
28 ) -> Result
<(), Error
> {
29 let mut signal_stream
= signal(SignalKind
::interrupt())?
;
30 let abort_count
= Arc
::new(AtomicUsize
::new(0));
31 let abort_count2
= Arc
::clone(&abort_count
);
33 let abort_future
= async
move {
34 while signal_stream
.recv().await
.is_some() {
35 log
::info
!("got shutdown request (SIGINT)");
36 let prev_count
= abort_count2
.fetch_add(1, Ordering
::SeqCst
);
38 log
::info
!("forced exit (task still running)");
45 let request_future
= async
move {
49 let upid_encoded
= percent_encode_component(upid_str
);
52 let abort
= abort_count
.load(Ordering
::Relaxed
);
54 if forward_interrupt
{
55 let path
= format
!("api2/json/nodes/localhost/tasks/{upid_encoded}");
56 let _
= client
.delete(&path
, None
).await?
;
62 let param
= json
!({ "start": start, "limit": limit, "test-status": true }
);
64 let path
= format
!("api2/json/nodes/localhost/tasks/{upid_encoded}/log");
65 let result
= client
.get(&path
, Some(param
)).await?
;
67 let active
= result
["active"].as_bool().unwrap();
68 let total
= result
["total"].as_u64().unwrap();
69 let data
= result
["data"].as_array().unwrap();
71 let lines
= data
.len();
74 let n
= item
["n"].as_u64().unwrap();
75 let t
= item
["t"].as_str().unwrap();
77 bail
!("got wrong line number in response data ({n} != {start}");
79 if strip_date
&& t
.len() > 27 && &t
[25..27] == ": " {
90 tokio
::time
::sleep(tokio
::time
::Duration
::from_millis(1000)).await
;
94 } else if lines
!= limit
{
95 bail
!("got wrong number of lines from server ({lines} != {limit})");
99 let status_path
= format
!("api2/json/nodes/localhost/tasks/{upid_encoded}/status");
100 let task_result
= &client
.get(&status_path
, None
).await?
["data"];
101 if task_result
["status"].as_str() == Some("stopped") {
102 match task_result
["exitstatus"].as_str() {
103 None
=> bail
!("task stopped with unknown status"),
104 Some(status
) if status
== "OK" || status
.starts_with("WARNINGS") => (),
105 Some(status
) => bail
!("task failed (status {status})"),
113 request
= request_future
.fuse() => request?
,
114 abort
= abort_future
.fuse() => abort?
,
120 /// Display task result (upid), or view task log - depending on output format
122 /// In case of a task log of a running task, this will forward interrupt signals
123 /// to the task and potentially abort it!
124 pub async
fn view_task_result(
128 ) -> Result
<(), Error
> {
129 let data
= &result
["data"];
130 if output_format
== "text" {
131 if let Some(upid
) = data
.as_str() {
132 display_task_log(client
, upid
, true, true).await?
;
135 format_and_print_result(data
, output_format
);