@@ -19,6 +19,9 @@ use std::ffi::OsString;
19
19
use std:: os:: unix:: process:: CommandExt ;
20
20
use std:: process:: Command ;
21
21
22
+ use crate :: spec:: HostSpec ;
23
+ use crate :: spec:: ImageReference ;
24
+
22
25
/// Perform an upgrade operation
23
26
#[ derive( Debug , Parser ) ]
24
27
pub ( crate ) struct UpgradeOpts {
@@ -174,9 +177,10 @@ pub(crate) async fn get_locked_sysroot() -> Result<ostree_ext::sysroot::SysrootL
174
177
#[ context( "Pulling" ) ]
175
178
async fn pull (
176
179
repo : & ostree:: Repo ,
177
- imgref : & OstreeImageReference ,
180
+ imgref : & ImageReference ,
178
181
quiet : bool ,
179
182
) -> Result < Box < LayeredImageState > > {
183
+ let imgref = & OstreeImageReference :: from ( imgref. clone ( ) ) ;
180
184
let config = Default :: default ( ) ;
181
185
let mut imp = ostree_container:: store:: ImageImporter :: new ( repo, imgref, config) . await ?;
182
186
let prep = match imp. prepare ( ) . await ? {
@@ -215,22 +219,35 @@ async fn pull(
215
219
async fn stage (
216
220
sysroot : & SysrootLock ,
217
221
stateroot : & str ,
218
- imgref : & ostree_container:: OstreeImageReference ,
219
222
image : Box < LayeredImageState > ,
220
- origin : & glib :: KeyFile ,
223
+ spec : & HostSpec ,
221
224
) -> Result < ( ) > {
222
225
let cancellable = gio:: Cancellable :: NONE ;
223
226
let stateroot = Some ( stateroot) ;
224
227
let merge_deployment = sysroot. merge_deployment ( stateroot) ;
228
+ let origin = glib:: KeyFile :: new ( ) ;
229
+ let ostree_imgref = spec
230
+ . image
231
+ . as_ref ( )
232
+ . map ( |imgref| OstreeImageReference :: from ( imgref. clone ( ) ) ) ;
233
+ if let Some ( imgref) = ostree_imgref. as_ref ( ) {
234
+ origin. set_string (
235
+ "origin" ,
236
+ ostree_container:: deploy:: ORIGIN_CONTAINER ,
237
+ imgref. to_string ( ) . as_str ( ) ,
238
+ ) ;
239
+ }
225
240
let _new_deployment = sysroot. stage_tree_with_options (
226
241
stateroot,
227
242
image. merge_commit . as_str ( ) ,
228
- Some ( origin) ,
243
+ Some ( & origin) ,
229
244
merge_deployment. as_ref ( ) ,
230
245
& Default :: default ( ) ,
231
246
cancellable,
232
247
) ?;
233
- println ! ( "Queued for next boot: {imgref}" ) ;
248
+ if let Some ( imgref) = ostree_imgref. as_ref ( ) {
249
+ println ! ( "Queued for next boot: {imgref}" ) ;
250
+ }
234
251
Ok ( ( ) )
235
252
}
236
253
@@ -266,30 +283,30 @@ async fn prepare_for_write() -> Result<()> {
266
283
async fn upgrade ( opts : UpgradeOpts ) -> Result < ( ) > {
267
284
prepare_for_write ( ) . await ?;
268
285
let sysroot = & get_locked_sysroot ( ) . await ?;
269
- let repo = & sysroot. repo ( ) ;
270
286
let booted_deployment = & sysroot. require_booted_deployment ( ) ?;
271
- let status = crate :: status:: DeploymentStatus :: from_deployment ( booted_deployment, true ) ?;
272
- let osname = booted_deployment. osname ( ) ;
273
- let origin = booted_deployment
274
- . origin ( )
275
- . ok_or_else ( || anyhow:: anyhow!( "Deployment is missing an origin" ) ) ?;
276
- let imgref = status
277
- . image
278
- . ok_or_else ( || anyhow:: anyhow!( "Booted deployment is not container image based" ) ) ?;
279
- let imgref: OstreeImageReference = imgref. into ( ) ;
280
- if !status. supported {
287
+ let ( _deployments, host) = crate :: status:: get_status ( sysroot, Some ( booted_deployment) ) ?;
288
+ // SAFETY: There must be a status if we have a booted deployment
289
+ let status = host. status . unwrap ( ) ;
290
+ let imgref = host. spec . image . as_ref ( ) ;
291
+ // If there's no specified image, let's be nice and check if the booted system is using rpm-ostree
292
+ if imgref. is_none ( ) && status. booted . map_or ( false , |b| b. incompatible ) {
281
293
return Err ( anyhow:: anyhow!(
282
294
"Booted deployment contains local rpm-ostree modifications; cannot upgrade via bootc"
283
295
) ) ;
284
296
}
285
- let commit = booted_deployment. csum ( ) ;
286
- let state = ostree_container:: store:: query_image_commit ( repo, & commit) ?;
287
- let digest = state. manifest_digest . as_str ( ) ;
288
-
297
+ let imgref = imgref. ok_or_else ( || anyhow:: anyhow!( "No image source specified" ) ) ?;
298
+ // Find the currently queued digest, if any before we pull
299
+ let queued_digest = status
300
+ . staged
301
+ . as_ref ( )
302
+ . and_then ( |e| e. image . as_ref ( ) )
303
+ . map ( |img| img. image_digest . as_str ( ) ) ;
289
304
if opts. check {
290
305
// pull the image manifest without the layers
291
306
let config = Default :: default ( ) ;
292
- let mut imp = ostree_container:: store:: ImageImporter :: new ( repo, & imgref, config) . await ?;
307
+ let imgref = & OstreeImageReference :: from ( imgref. clone ( ) ) ;
308
+ let mut imp =
309
+ ostree_container:: store:: ImageImporter :: new ( & sysroot. repo ( ) , imgref, config) . await ?;
293
310
match imp. prepare ( ) . await ? {
294
311
PrepareResult :: AlreadyPresent ( c) => {
295
312
println ! (
@@ -298,24 +315,27 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
298
315
) ;
299
316
return Ok ( ( ) ) ;
300
317
}
301
- PrepareResult :: Ready ( p) => {
318
+ PrepareResult :: Ready ( r) => {
319
+ // TODO show a diff
302
320
println ! (
303
- "New manifest available for {}. Digest {}" ,
304
- imgref , p . manifest_digest
321
+ "New image available for {imgref }. Digest {}" ,
322
+ r . manifest_digest
305
323
) ;
324
+ // Note here we'll fall through to handling the --touch-if-changed below
306
325
}
307
326
}
308
327
} else {
309
- let fetched = pull ( repo, & imgref, opts. quiet ) . await ?;
310
-
311
- if fetched. merge_commit . as_str ( ) == commit. as_str ( ) {
312
- println ! ( "Already queued: {digest}" ) ;
313
- return Ok ( ( ) ) ;
328
+ let fetched = pull ( & sysroot. repo ( ) , imgref, opts. quiet ) . await ?;
329
+ if let Some ( queued_digest) = queued_digest {
330
+ if fetched. merge_commit . as_str ( ) == queued_digest {
331
+ println ! ( "Already queued: {queued_digest}" ) ;
332
+ return Ok ( ( ) ) ;
333
+ }
314
334
}
315
335
316
- stage ( sysroot, & osname, & imgref, fetched, & origin) . await ?;
336
+ let osname = booted_deployment. osname ( ) ;
337
+ stage ( sysroot, & osname, fetched, & host. spec ) . await ?;
317
338
}
318
-
319
339
if let Some ( path) = opts. touch_if_changed {
320
340
std:: fs:: write ( & path, "" ) . with_context ( || format ! ( "Writing {path}" ) ) ?;
321
341
}
@@ -327,14 +347,14 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
327
347
#[ context( "Switching" ) ]
328
348
async fn switch ( opts : SwitchOpts ) -> Result < ( ) > {
329
349
prepare_for_write ( ) . await ?;
330
-
331
350
let cancellable = gio:: Cancellable :: NONE ;
332
- let sysroot = get_locked_sysroot ( ) . await ?;
333
- let booted_deployment = & sysroot. require_booted_deployment ( ) ?;
334
- let ( origin, booted_image) = crate :: utils:: get_image_origin ( booted_deployment) ?;
335
- let booted_refspec = origin. optional_string ( "origin" , "refspec" ) ?;
336
- let osname = booted_deployment. osname ( ) ;
351
+
352
+ let sysroot = & get_locked_sysroot ( ) . await ?;
337
353
let repo = & sysroot. repo ( ) ;
354
+ let booted_deployment = & sysroot. require_booted_deployment ( ) ?;
355
+ let ( _deployments, host) = crate :: status:: get_status ( sysroot, Some ( booted_deployment) ) ?;
356
+ // SAFETY: There must be a status if we have a booted deployment
357
+ let status = host. status . unwrap ( ) ;
338
358
339
359
let transport = ostree_container:: Transport :: try_from ( opts. transport . as_str ( ) ) ?;
340
360
let imgref = ostree_container:: ImageReference {
@@ -349,30 +369,38 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
349
369
SignatureSource :: ContainerPolicy
350
370
} ;
351
371
let target = ostree_container:: OstreeImageReference { sigverify, imgref } ;
372
+ let target = ImageReference :: from ( target) ;
373
+
374
+ let new_spec = {
375
+ let mut new_spec = host. spec . clone ( ) ;
376
+ new_spec. image = Some ( target. clone ( ) ) ;
377
+ new_spec
378
+ } ;
379
+
380
+ if new_spec == host. spec {
381
+ anyhow:: bail!( "No changes in current host spec" ) ;
382
+ }
352
383
353
384
let fetched = pull ( repo, & target, opts. quiet ) . await ?;
354
385
355
386
if !opts. retain {
356
387
// By default, we prune the previous ostree ref or container image
357
- if let Some ( ostree_ref) = booted_refspec {
358
- let ( remote, ostree_ref) =
359
- ostree:: parse_refspec ( & ostree_ref) . context ( "Failed to parse ostree ref" ) ?;
360
- repo. set_ref_immediate ( remote. as_deref ( ) , & ostree_ref, None , cancellable) ?;
361
- origin. remove_key ( "origin" , "refspec" ) ?;
362
- } else if let Some ( booted_image) = booted_image. as_ref ( ) {
363
- ostree_container:: store:: remove_image ( repo, & booted_image. imgref ) ?;
364
- let _nlayers: u32 = ostree_container:: store:: gc_image_layers ( repo) ?;
388
+ if let Some ( booted_origin) = booted_deployment. origin ( ) {
389
+ if let Some ( ostree_ref) = booted_origin. optional_string ( "origin" , "refspec" ) ? {
390
+ let ( remote, ostree_ref) =
391
+ ostree:: parse_refspec ( & ostree_ref) . context ( "Failed to parse ostree ref" ) ?;
392
+ repo. set_ref_immediate ( remote. as_deref ( ) , & ostree_ref, None , cancellable) ?;
393
+ } else if let Some ( booted_image) = status. booted . as_ref ( ) . and_then ( |b| b. image . as_ref ( ) )
394
+ {
395
+ let imgref = OstreeImageReference :: from ( booted_image. image . clone ( ) ) ;
396
+ ostree_container:: store:: remove_image ( repo, & imgref. imgref ) ?;
397
+ let _nlayers: u32 = ostree_container:: store:: gc_image_layers ( repo) ?;
398
+ }
365
399
}
366
400
}
367
401
368
- // We always make a fresh origin to toss out old state.
369
- let origin = glib:: KeyFile :: new ( ) ;
370
- origin. set_string (
371
- "origin" ,
372
- ostree_container:: deploy:: ORIGIN_CONTAINER ,
373
- target. to_string ( ) . as_str ( ) ,
374
- ) ;
375
- stage ( & sysroot, & osname, & target, fetched, & origin) . await ?;
402
+ let stateroot = booted_deployment. osname ( ) ;
403
+ stage ( sysroot, & stateroot, fetched, & new_spec) . await ?;
376
404
377
405
Ok ( ( ) )
378
406
}
0 commit comments