@@ -3,19 +3,25 @@ use std::collections::VecDeque;
3
3
use std:: io:: IsTerminal ;
4
4
use std:: io:: Read ;
5
5
use std:: io:: Write ;
6
+ use std:: str:: FromStr ;
6
7
7
8
use anyhow:: { Context , Result } ;
9
+ use cap_std_ext:: cap_std;
8
10
use fn_error_context:: context;
9
11
use ostree:: glib;
10
12
use ostree_container:: OstreeImageReference ;
11
13
use ostree_ext:: container as ostree_container;
14
+ use ostree_ext:: container:: deploy:: ORIGIN_CONTAINER ;
12
15
use ostree_ext:: container_utils:: composefs_booted;
13
16
use ostree_ext:: container_utils:: ostree_booted;
17
+ use ostree_ext:: containers_image_proxy;
14
18
use ostree_ext:: keyfileext:: KeyFileExt ;
15
19
use ostree_ext:: oci_spec;
16
20
use ostree_ext:: ostree;
21
+ use tokio:: io:: AsyncReadExt ;
17
22
18
23
use crate :: cli:: OutputFormat ;
24
+ use crate :: spec:: ImageStatus ;
19
25
use crate :: spec:: { BootEntry , BootOrder , Host , HostSpec , HostStatus , HostType } ;
20
26
use crate :: spec:: { ImageReference , ImageSignature } ;
21
27
use crate :: store:: { CachedImageStatus , ContainerImageStore , Storage } ;
@@ -287,6 +293,140 @@ pub(crate) fn get_status(
287
293
Ok ( ( deployments, host) )
288
294
}
289
295
296
+ #[ context( "Getting composefs deployment metadata" ) ]
297
+ async fn boot_entry_from_composefs_deployment (
298
+ origin : tini:: Ini ,
299
+ booted : & String ,
300
+ ) -> Result < BootEntry > {
301
+ let image = match origin. get :: < String > ( "origin" , ORIGIN_CONTAINER ) {
302
+ Some ( img_name_from_config) => {
303
+ let ostree_img_ref = OstreeImageReference :: from_str ( & img_name_from_config) ?;
304
+ let imgref = ostree_img_ref. imgref . to_string ( ) ;
305
+
306
+ let img_ref = ImageReference :: from ( ostree_img_ref) ;
307
+
308
+ let config = containers_image_proxy:: ImageProxyConfig :: default ( ) ;
309
+ let proxy = containers_image_proxy:: ImageProxy :: new_with_config ( config) . await ?;
310
+
311
+ // The image might've been removed, so don't error if we can't get the image manifest
312
+ let ( image_digest, version, architecture, created_at) =
313
+ match proxy. open_image ( & imgref) . await . context ( "Opening image" ) {
314
+ Ok ( img) => {
315
+ let ( _, manifest) = proxy. fetch_manifest ( & img) . await ?;
316
+ let ( mut reader, driver) =
317
+ proxy. get_descriptor ( & img, manifest. config ( ) ) . await ?;
318
+
319
+ let mut buf = Vec :: with_capacity ( manifest. config ( ) . size ( ) as usize ) ;
320
+ buf. resize ( manifest. config ( ) . size ( ) as usize , 0 ) ;
321
+ reader. read_exact ( & mut buf) . await ?;
322
+ driver. await ?;
323
+
324
+ let config: oci_spec:: image:: ImageConfiguration =
325
+ serde_json:: from_slice ( & buf) ?;
326
+
327
+ let digest = manifest. config ( ) . digest ( ) . to_string ( ) ;
328
+ let arch = config. architecture ( ) . to_string ( ) ;
329
+ let created = config. created ( ) . clone ( ) ;
330
+ let version = manifest
331
+ . annotations ( )
332
+ . as_ref ( )
333
+ . and_then ( |a| a. get ( oci_spec:: image:: ANNOTATION_VERSION ) . cloned ( ) ) ;
334
+
335
+ ( digest, version, arch, created)
336
+ }
337
+
338
+ Err ( e) => {
339
+ tracing:: debug!( "Failed to open image {img_ref}, because {e:?}" ) ;
340
+ ( "" . into ( ) , None , "" . into ( ) , None )
341
+ }
342
+ } ;
343
+
344
+ let timestamp = if let Some ( created_at) = created_at {
345
+ try_deserialize_timestamp ( & created_at)
346
+ } else {
347
+ None
348
+ } ;
349
+
350
+ let image_status = ImageStatus {
351
+ image : img_ref,
352
+ version,
353
+ timestamp,
354
+ image_digest,
355
+ architecture,
356
+ } ;
357
+
358
+ Some ( image_status)
359
+ }
360
+
361
+ // Wasn't booted using a container image. Do nothing
362
+ None => None ,
363
+ } ;
364
+
365
+ let e = BootEntry {
366
+ image,
367
+ cached_update : None ,
368
+ incompatible : false ,
369
+ pinned : false ,
370
+ store : None ,
371
+ ostree : None ,
372
+ composefs : Some ( crate :: spec:: BootEntryComposefs {
373
+ verity : booted. into ( ) ,
374
+ } ) ,
375
+ } ;
376
+
377
+ return Ok ( e) ;
378
+ }
379
+
380
+ #[ context( "Getting composefs deployment status" ) ]
381
+ pub ( crate ) async fn composefs_deployment_status ( ) -> Result < Host > {
382
+ let cmdline = crate :: kernel:: parse_cmdline ( ) ?;
383
+ let booted = cmdline. iter ( ) . find_map ( |x| x. strip_prefix ( "composefs=" ) ) ;
384
+
385
+ let Some ( booted) = booted else {
386
+ anyhow:: bail!( "Failed to find composefs parameter in kernel cmdline" ) ;
387
+ } ;
388
+
389
+ let sysroot = cap_std:: fs:: Dir :: open_ambient_dir ( "/sysroot" , cap_std:: ambient_authority ( ) )
390
+ . context ( "Opening sysroot" ) ?;
391
+ let deployments = sysroot
392
+ . read_dir ( "state/deploy" )
393
+ . context ( "Reading sysroot state/deploy" ) ?;
394
+
395
+ let host_spec = HostSpec {
396
+ image : None ,
397
+ boot_order : BootOrder :: Default ,
398
+ } ;
399
+
400
+ let mut host = Host :: new ( host_spec) ;
401
+
402
+ for depl in deployments {
403
+ let depl = depl?;
404
+
405
+ let depl_file_name = depl. file_name ( ) ;
406
+ let depl_file_name = depl_file_name. to_string_lossy ( ) ;
407
+
408
+ // read the origin file
409
+ let config = depl
410
+ . open_dir ( )
411
+ . with_context ( || format ! ( "Failed to open {depl_file_name}" ) ) ?
412
+ . read_to_string ( format ! ( "{depl_file_name}.origin" ) )
413
+ . with_context ( || format ! ( "Reading file {depl_file_name}.origin" ) ) ?;
414
+
415
+ let ini = tini:: Ini :: from_string ( & config)
416
+ . with_context ( || format ! ( "Failed to parse file {depl_file_name}.origin as ini" ) ) ?;
417
+
418
+ let boot_entry = boot_entry_from_composefs_deployment ( ini, & booted. into ( ) ) . await ?;
419
+
420
+ if depl. file_name ( ) == booted {
421
+ host. spec . image = boot_entry. image . as_ref ( ) . map ( |x| x. image . clone ( ) ) ;
422
+ host. status . booted = Some ( boot_entry) ;
423
+ continue ;
424
+ }
425
+ }
426
+
427
+ Ok ( host)
428
+ }
429
+
290
430
/// Implementation of the `bootc status` CLI command.
291
431
#[ context( "Status" ) ]
292
432
pub ( crate ) async fn status ( opts : super :: cli:: StatusOpts ) -> Result < ( ) > {
@@ -301,45 +441,7 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
301
441
let ( _deployments, host) = get_status ( & sysroot, booted_deployment. as_ref ( ) ) ?;
302
442
host
303
443
} else if composefs_booted ( ) ? {
304
- let dir_contents = std:: fs:: read_dir ( "/sysroot/composefs/images" ) ?;
305
-
306
- let host_spec = HostSpec {
307
- image : Some ( ImageReference {
308
- image : "" . into ( ) ,
309
- transport : "" . into ( ) ,
310
- signature : None ,
311
- } ) ,
312
- boot_order : BootOrder :: Default ,
313
- } ;
314
-
315
- let mut host = Host :: new ( host_spec) ;
316
-
317
- let cmdline = crate :: kernel:: parse_cmdline ( ) ?;
318
- let booted = cmdline. iter ( ) . find_map ( |x| x. strip_prefix ( "composefs=" ) ) ;
319
-
320
- let Some ( booted) = booted else {
321
- anyhow:: bail!( "Failed to find composefs parameter in kernel cmdline" ) ;
322
- } ;
323
-
324
- host. status = HostStatus {
325
- staged : None ,
326
- booted : Some ( BootEntry {
327
- image : None ,
328
- cached_update : None ,
329
- incompatible : false ,
330
- pinned : false ,
331
- store : None ,
332
- ostree : None ,
333
- composefs : Some ( crate :: spec:: BootEntryComposefs {
334
- verity : booted. into ( ) ,
335
- } ) ,
336
- } ) ,
337
- rollback : None ,
338
- rollback_queued : false ,
339
- ty : None ,
340
- } ;
341
-
342
- host
444
+ composefs_deployment_status ( ) . await ?
343
445
} else {
344
446
Default :: default ( )
345
447
} ;
0 commit comments