@@ -16,12 +16,15 @@ use tokio::sync::Mutex;
16
16
// Maximum number of times per second that an event will be written.
17
17
const REFRESH_HZ : u16 = 5 ;
18
18
19
- pub const API_VERSION : & str = "org.containers.bootc.progress/v1" ;
19
+ /// Semantic version of the protocol.
20
+ const API_VERSION : & str = "0.1.0" ;
20
21
21
22
/// An incremental update to e.g. a container image layer download.
22
23
/// The first time a given "subtask" name is seen, a new progress bar should be created.
23
24
/// If bytes == bytes_total, then the subtask is considered complete.
24
- #[ derive( Debug , serde:: Serialize , serde:: Deserialize , Default , Clone , JsonSchema ) ]
25
+ #[ derive(
26
+ Debug , serde:: Serialize , serde:: Deserialize , Default , Clone , JsonSchema , PartialEq , Eq ,
27
+ ) ]
25
28
#[ serde( rename_all = "camelCase" ) ]
26
29
pub struct SubTaskBytes < ' t > {
27
30
/// A machine readable type for the task (used for i18n).
@@ -45,7 +48,9 @@ pub struct SubTaskBytes<'t> {
45
48
}
46
49
47
50
/// Marks the beginning and end of a dictrete step
48
- #[ derive( Debug , serde:: Serialize , serde:: Deserialize , Default , Clone , JsonSchema ) ]
51
+ #[ derive(
52
+ Debug , serde:: Serialize , serde:: Deserialize , Default , Clone , JsonSchema , PartialEq , Eq ,
53
+ ) ]
49
54
#[ serde( rename_all = "camelCase" ) ]
50
55
pub struct SubTaskStep < ' t > {
51
56
/// A machine readable type for the task (used for i18n).
@@ -65,18 +70,20 @@ pub struct SubTaskStep<'t> {
65
70
}
66
71
67
72
/// An event emitted as JSON.
68
- #[ derive( Debug , serde:: Serialize , serde:: Deserialize , JsonSchema ) ]
73
+ #[ derive( Debug , Clone , serde:: Serialize , serde:: Deserialize , JsonSchema , PartialEq , Eq ) ]
69
74
#[ serde(
70
75
tag = "type" ,
71
76
rename_all = "PascalCase" ,
72
77
rename_all_fields = "camelCase"
73
78
) ]
74
79
pub enum Event < ' t > {
80
+ Start {
81
+ /// The semantic version of the progress protocol.
82
+ #[ serde( borrow) ]
83
+ version : Cow < ' t , str > ,
84
+ } ,
75
85
/// An incremental update to a container image layer download
76
86
ProgressBytes {
77
- /// The version of the progress event format.
78
- #[ serde( borrow) ]
79
- api_version : Cow < ' t , str > ,
80
87
/// A machine readable type (e.g., pulling) for the task (used for i18n
81
88
/// and UI customization).
82
89
#[ serde( borrow) ]
@@ -106,9 +113,6 @@ pub enum Event<'t> {
106
113
} ,
107
114
/// An incremental update with discrete steps
108
115
ProgressSteps {
109
- /// The version of the progress event format.
110
- #[ serde( borrow) ]
111
- api_version : Cow < ' t , str > ,
112
116
/// A machine readable type (e.g., pulling) for the task (used for i18n
113
117
/// and UI customization).
114
118
#[ serde( borrow) ]
@@ -150,6 +154,8 @@ impl FromStr for RawProgressFd {
150
154
151
155
#[ derive( Debug ) ]
152
156
struct ProgressWriterInner {
157
+ /// true if we sent the initial Start message
158
+ sent_start : bool ,
153
159
last_write : Option < std:: time:: Instant > ,
154
160
fd : BufWriter < Sender > ,
155
161
}
@@ -171,6 +177,7 @@ impl TryFrom<OwnedFd> for ProgressWriter {
171
177
impl From < Sender > for ProgressWriter {
172
178
fn from ( value : Sender ) -> Self {
173
179
let inner = ProgressWriterInner {
180
+ sent_start : false ,
174
181
last_write : None ,
175
182
fd : BufWriter :: new ( value) ,
176
183
} ;
@@ -190,6 +197,18 @@ impl TryFrom<RawProgressFd> for ProgressWriter {
190
197
}
191
198
192
199
impl ProgressWriter {
200
+ /// Serialize the target value as a single line of JSON and write it.
201
+ async fn send_impl_inner < T : Serialize > ( inner : & mut ProgressWriterInner , v : T ) -> Result < ( ) > {
202
+ // serde is guaranteed not to output newlines here
203
+ let buf = serde_json:: to_vec ( & v) ?;
204
+ inner. fd . write_all ( & buf) . await ?;
205
+ // We always end in a newline
206
+ inner. fd . write_all ( b"\n " ) . await ?;
207
+ // And flush to ensure the remote side sees updates immediately
208
+ inner. fd . flush ( ) . await ?;
209
+ Ok ( ( ) )
210
+ }
211
+
193
212
/// Serialize the target object to JSON as a single line
194
213
pub ( crate ) async fn send_impl < T : Serialize > ( & self , v : T , required : bool ) -> Result < ( ) > {
195
214
let mut guard = self . inner . lock ( ) . await ;
@@ -198,8 +217,17 @@ impl ProgressWriter {
198
217
return Ok ( ( ) ) ;
199
218
} ;
200
219
220
+ // If this is our first message, emit the Start message
221
+ if !inner. sent_start {
222
+ inner. sent_start = true ;
223
+ let start = Event :: Start {
224
+ version : API_VERSION . into ( ) ,
225
+ } ;
226
+ Self :: send_impl_inner ( inner, & start) . await ?;
227
+ }
228
+
201
229
// For messages that can be dropped, if we already sent an update within this cycle, discard this one.
202
- // TODO: Also consider querying the pipe buffer and also dropping if we can't do this write.
230
+ // TODO: Also consider querying the pipe buffer and also dropping if wqe can't do this write.
203
231
let now = Instant :: now ( ) ;
204
232
if !required {
205
233
const REFRESH_MS : u32 = 1000 / REFRESH_HZ as u32 ;
@@ -210,22 +238,15 @@ impl ProgressWriter {
210
238
}
211
239
}
212
240
213
- // SAFETY: Propagating panics from the mutex here is intentional
214
- // serde is guaranteed not to output newlines here
215
- let buf = serde_json:: to_vec ( & v) ?;
216
- inner. fd . write_all ( & buf) . await ?;
217
- // We always end in a newline
218
- inner. fd . write_all ( b"\n " ) . await ?;
219
- // And flush to ensure the remote side sees updates immediately
220
- inner. fd . flush ( ) . await ?;
241
+ Self :: send_impl_inner ( inner, & v) . await ?;
221
242
// Update the last write time
222
243
inner. last_write = Some ( now) ;
223
244
Ok ( ( ) )
224
245
}
225
246
226
247
/// Send an event.
227
- pub ( crate ) async fn send < T : Serialize > ( & self , v : T ) {
228
- if let Err ( e) = self . send_impl ( v , true ) . await {
248
+ pub ( crate ) async fn send ( & self , event : Event < ' _ > ) {
249
+ if let Err ( e) = self . send_impl ( event , true ) . await {
229
250
eprintln ! ( "Failed to write to jsonl: {}" , e) ;
230
251
// Stop writing to fd but let process continue
231
252
// SAFETY: Propagating panics from the mutex here is intentional
@@ -234,8 +255,8 @@ impl ProgressWriter {
234
255
}
235
256
236
257
/// Send an event that can be dropped.
237
- pub ( crate ) async fn send_lossy < T : Serialize > ( & self , v : T ) {
238
- if let Err ( e) = self . send_impl ( v , false ) . await {
258
+ pub ( crate ) async fn send_lossy ( & self , event : Event < ' _ > ) {
259
+ if let Err ( e) = self . send_impl ( event , false ) . await {
239
260
eprintln ! ( "Failed to write to jsonl: {}" , e) ;
240
261
// Stop writing to fd but let process continue
241
262
// SAFETY: Propagating panics from the mutex here is intentional
@@ -272,18 +293,30 @@ mod test {
272
293
#[ tokio:: test]
273
294
async fn test_jsonl ( ) -> Result < ( ) > {
274
295
let testvalues = [
275
- S {
276
- s : "foo" . into ( ) ,
277
- v : 42 ,
296
+ Event :: ProgressSteps {
297
+ task : "sometask" . into ( ) ,
298
+ description : "somedesc" . into ( ) ,
299
+ id : "someid" . into ( ) ,
300
+ steps_cached : 0 ,
301
+ steps : 0 ,
302
+ steps_total : 3 ,
303
+ subtasks : Vec :: new ( ) ,
278
304
} ,
279
- S {
280
- // Test with an embedded newline to sanity check that serde doesn't write it literally
281
- s : "foo\n bar" . into ( ) ,
282
- v : 0 ,
305
+ Event :: ProgressBytes {
306
+ task : "sometask" . into ( ) ,
307
+ description : "somedesc" . into ( ) ,
308
+ id : "someid" . into ( ) ,
309
+ bytes_cached : 0 ,
310
+ bytes : 11 ,
311
+ bytes_total : 42 ,
312
+ steps_cached : 0 ,
313
+ steps : 0 ,
314
+ steps_total : 3 ,
315
+ subtasks : Vec :: new ( ) ,
283
316
} ,
284
317
] ;
285
318
let ( send, recv) = tokio:: net:: unix:: pipe:: pipe ( ) ?;
286
- let testvalues_sender = & testvalues;
319
+ let testvalues_sender = testvalues. iter ( ) . cloned ( ) ;
287
320
let sender = async move {
288
321
let w = ProgressWriter :: try_from ( send) ?;
289
322
for value in testvalues_sender {
@@ -296,10 +329,18 @@ mod test {
296
329
let tf = BufReader :: new ( recv) ;
297
330
let mut expected = testvalues. iter ( ) ;
298
331
let mut lines = tf. lines ( ) ;
332
+ let mut got_first = false ;
299
333
while let Some ( line) = lines. next_line ( ) . await ? {
300
- let found: S = serde_json:: from_str ( & line) ?;
301
- let expected = expected. next ( ) . unwrap ( ) ;
302
- assert_eq ! ( & found, expected) ;
334
+ let found: Event = serde_json:: from_str ( & line) ?;
335
+ let expected_value = if !got_first {
336
+ got_first = true ;
337
+ & Event :: Start {
338
+ version : API_VERSION . into ( ) ,
339
+ }
340
+ } else {
341
+ expected. next ( ) . unwrap ( )
342
+ } ;
343
+ assert_eq ! ( & found, expected_value) ;
303
344
}
304
345
anyhow:: Ok ( ( ) )
305
346
} ;
0 commit comments