2
2
//!
3
3
//! Create a merged filesystem tree with the image and mounted configmaps.
4
4
5
+ use anyhow:: Ok ;
5
6
use anyhow:: { Context , Result } ;
6
7
8
+ use cap_std:: fs:: Dir ;
9
+ use cap_std_ext:: cap_std;
10
+ use cap_std_ext:: dirext:: CapStdExtDirExt ;
7
11
use fn_error_context:: context;
8
12
use ostree:: { gio, glib} ;
9
13
use ostree_container:: OstreeImageReference ;
@@ -12,6 +16,7 @@ use ostree_ext::container::store::PrepareResult;
12
16
use ostree_ext:: ostree;
13
17
use ostree_ext:: ostree:: Deployment ;
14
18
use ostree_ext:: sysroot:: SysrootLock ;
19
+ use rustix:: fs:: MetadataExt ;
15
20
16
21
use crate :: spec:: HostSpec ;
17
22
use crate :: spec:: ImageReference ;
@@ -202,6 +207,18 @@ async fn deploy(
202
207
Ok ( ( ) )
203
208
}
204
209
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
+
205
222
/// Stage (queue deployment of) a fetched container image.
206
223
#[ context( "Staging" ) ]
207
224
pub ( crate ) async fn stage (
@@ -211,13 +228,7 @@ pub(crate) async fn stage(
211
228
spec : & RequiredHostSpec < ' _ > ,
212
229
) -> Result < ( ) > {
213
230
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 ) ?;
221
232
crate :: deploy:: deploy (
222
233
sysroot,
223
234
merge_deployment. as_ref ( ) ,
@@ -227,11 +238,115 @@ pub(crate) async fn stage(
227
238
)
228
239
. await ?;
229
240
crate :: deploy:: cleanup ( sysroot) . await ?;
230
- println ! ( "Queued for next boot: {imgref}" ) ;
241
+ println ! ( "Queued for next boot: {}" , spec . image ) ;
231
242
if let Some ( version) = image. version . as_deref ( ) {
232
243
println ! ( " Version: {version}" ) ;
233
244
}
234
245
println ! ( " Digest: {}" , image. manifest_digest) ;
235
246
236
247
Ok ( ( ) )
237
248
}
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