22//!
33//! Create a merged filesystem tree with the image and mounted configmaps.
44
5+ use anyhow:: Ok ;
56use anyhow:: { Context , Result } ;
67
8+ use cap_std:: fs:: Dir ;
9+ use cap_std_ext:: cap_std;
10+ use cap_std_ext:: dirext:: CapStdExtDirExt ;
711use fn_error_context:: context;
812use ostree:: { gio, glib} ;
913use ostree_container:: OstreeImageReference ;
@@ -12,6 +16,7 @@ use ostree_ext::container::store::PrepareResult;
1216use ostree_ext:: ostree;
1317use ostree_ext:: ostree:: Deployment ;
1418use ostree_ext:: sysroot:: SysrootLock ;
19+ use rustix:: fs:: MetadataExt ;
1520
1621use crate :: spec:: HostSpec ;
1722use crate :: spec:: ImageReference ;
@@ -202,6 +207,18 @@ async fn deploy(
202207 Ok ( ( ) )
203208}
204209
210+ #[ context( "Generating origin" ) ]
211+ fn origin_from_imageref ( imgref : & ImageReference ) -> Result < glib:: KeyFile > {
212+ let origin = glib:: KeyFile :: new ( ) ;
213+ let imgref = OstreeImageReference :: from ( imgref. clone ( ) ) ;
214+ origin. set_string (
215+ "origin" ,
216+ ostree_container:: deploy:: ORIGIN_CONTAINER ,
217+ imgref. to_string ( ) . as_str ( ) ,
218+ ) ;
219+ Ok ( origin)
220+ }
221+
205222/// Stage (queue deployment of) a fetched container image.
206223#[ context( "Staging" ) ]
207224pub ( crate ) async fn stage (
@@ -211,13 +228,7 @@ pub(crate) async fn stage(
211228 spec : & RequiredHostSpec < ' _ > ,
212229) -> Result < ( ) > {
213230 let merge_deployment = sysroot. merge_deployment ( Some ( stateroot) ) ;
214- let origin = glib:: KeyFile :: new ( ) ;
215- let imgref = OstreeImageReference :: from ( spec. image . clone ( ) ) ;
216- origin. set_string (
217- "origin" ,
218- ostree_container:: deploy:: ORIGIN_CONTAINER ,
219- imgref. to_string ( ) . as_str ( ) ,
220- ) ;
231+ let origin = origin_from_imageref ( spec. image ) ?;
221232 crate :: deploy:: deploy (
222233 sysroot,
223234 merge_deployment. as_ref ( ) ,
@@ -227,11 +238,115 @@ pub(crate) async fn stage(
227238 )
228239 . await ?;
229240 crate :: deploy:: cleanup ( sysroot) . await ?;
230- println ! ( "Queued for next boot: {imgref}" ) ;
241+ println ! ( "Queued for next boot: {}" , spec . image ) ;
231242 if let Some ( version) = image. version . as_deref ( ) {
232243 println ! ( " Version: {version}" ) ;
233244 }
234245 println ! ( " Digest: {}" , image. manifest_digest) ;
235246
236247 Ok ( ( ) )
237248}
249+
250+ fn find_newest_deployment_name ( deploysdir : & Dir ) -> Result < String > {
251+ let mut dirs = Vec :: new ( ) ;
252+ for ent in deploysdir. entries ( ) ? {
253+ let ent = ent?;
254+ if !ent. file_type ( ) ?. is_dir ( ) {
255+ continue ;
256+ }
257+ let name = ent. file_name ( ) ;
258+ let name = if let Some ( name) = name. to_str ( ) {
259+ name
260+ } else {
261+ continue ;
262+ } ;
263+ dirs. push ( ( name. to_owned ( ) , ent. metadata ( ) ?. mtime ( ) ) ) ;
264+ }
265+ dirs. sort_unstable_by ( |a, b| a. 1 . cmp ( & b. 1 ) ) ;
266+ if let Some ( ( name, _ts) ) = dirs. pop ( ) {
267+ Ok ( name)
268+ } else {
269+ anyhow:: bail!( "No deployment directory found" )
270+ }
271+ }
272+
273+ // Implementation of `bootc switch --in-place`
274+ pub ( crate ) fn switch_origin_inplace ( root : & Dir , imgref : & ImageReference ) -> Result < String > {
275+ // First, just create the new origin file
276+ let origin = origin_from_imageref ( imgref) ?;
277+ let serialized_origin = origin. to_data ( ) ;
278+
279+ // Now, we can't rely on being officially booted (e.g. with the `ostree=` karg)
280+ // in a scenario like running in the anaconda %post.
281+ // Eventually, we should support a setup here where ostree-prepare-root
282+ // can officially be run to "enter" an ostree root in a supportable way.
283+ // Anyways for now, the brutal hack is to just scrape through the deployments
284+ // and find the newest one, which we will mutate. If there's more than one,
285+ // ultimately the calling tooling should be fixed to set things up correctly.
286+
287+ let mut ostree_deploys = root. open_dir ( "sysroot/ostree/deploy" ) ?. entries ( ) ?;
288+ let deploydir = loop {
289+ if let Some ( ent) = ostree_deploys. next ( ) {
290+ let ent = ent?;
291+ if !ent. file_type ( ) ?. is_dir ( ) {
292+ continue ;
293+ }
294+ tracing:: debug!( "Checking {:?}" , ent. file_name( ) ) ;
295+ let child_dir = ent
296+ . open_dir ( )
297+ . with_context ( || format ! ( "Opening dir {:?}" , ent. file_name( ) ) ) ?;
298+ if let Some ( d) = child_dir. open_dir_optional ( "deploy" ) ? {
299+ break d;
300+ }
301+ } else {
302+ anyhow:: bail!( "Failed to find a deployment" ) ;
303+ }
304+ } ;
305+ let newest_deployment = find_newest_deployment_name ( & deploydir) ?;
306+ let origin_path = format ! ( "{newest_deployment}.origin" ) ;
307+ if !deploydir. try_exists ( & origin_path) ? {
308+ tracing:: warn!( "No extant origin for {newest_deployment}" ) ;
309+ }
310+ deploydir
311+ . atomic_write ( & origin_path, serialized_origin. as_bytes ( ) )
312+ . context ( "Writing origin" ) ?;
313+ return Ok ( newest_deployment) ;
314+ }
315+
316+ #[ test]
317+ fn test_switch_inplace ( ) -> Result < ( ) > {
318+ use std:: os:: unix:: fs:: DirBuilderExt ;
319+
320+ let td = cap_std_ext:: cap_tempfile:: TempDir :: new ( cap_std:: ambient_authority ( ) ) ?;
321+ let mut builder = cap_std:: fs:: DirBuilder :: new ( ) ;
322+ let builder = builder. recursive ( true ) . mode ( 0o755 ) ;
323+ let deploydir = "sysroot/ostree/deploy/default/deploy" ;
324+ let target_deployment = "af36eb0086bb55ac601600478c6168f834288013d60f8870b7851f44bf86c3c5.0" ;
325+ td. ensure_dir_with (
326+ format ! ( "sysroot/ostree/deploy/default/deploy/{target_deployment}" ) ,
327+ builder,
328+ ) ?;
329+ let deploydir = & td. open_dir ( deploydir) ?;
330+ let orig_imgref = ImageReference {
331+ image : "quay.io/exampleos/original:sometag" . into ( ) ,
332+ transport : "registry" . into ( ) ,
333+ signature : None ,
334+ } ;
335+ {
336+ let origin = origin_from_imageref ( & orig_imgref) ?;
337+ deploydir. atomic_write (
338+ format ! ( "{target_deployment}.origin" ) ,
339+ origin. to_data ( ) . as_bytes ( ) ,
340+ ) ?;
341+ }
342+
343+ let target_imgref = ImageReference {
344+ image : "quay.io/someother/otherimage:latest" . into ( ) ,
345+ transport : "registry" . into ( ) ,
346+ signature : None ,
347+ } ;
348+
349+ let replaced = switch_origin_inplace ( & td, & target_imgref) . unwrap ( ) ;
350+ assert_eq ! ( replaced, target_deployment) ;
351+ Ok ( ( ) )
352+ }
0 commit comments