@@ -19,6 +19,9 @@ use std::ffi::OsString;
1919use std:: os:: unix:: process:: CommandExt ;
2020use std:: process:: Command ;
2121
22+ use crate :: spec:: HostSpec ;
23+ use crate :: spec:: ImageReference ;
24+
2225/// Perform an upgrade operation
2326#[ derive( Debug , Parser ) ]
2427pub ( crate ) struct UpgradeOpts {
@@ -174,9 +177,10 @@ pub(crate) async fn get_locked_sysroot() -> Result<ostree_ext::sysroot::SysrootL
174177#[ context( "Pulling" ) ]
175178async fn pull (
176179 repo : & ostree:: Repo ,
177- imgref : & OstreeImageReference ,
180+ imgref : & ImageReference ,
178181 quiet : bool ,
179182) -> Result < Box < LayeredImageState > > {
183+ let imgref = & OstreeImageReference :: from ( imgref. clone ( ) ) ;
180184 let config = Default :: default ( ) ;
181185 let mut imp = ostree_container:: store:: ImageImporter :: new ( repo, imgref, config) . await ?;
182186 let prep = match imp. prepare ( ) . await ? {
@@ -215,22 +219,35 @@ async fn pull(
215219async fn stage (
216220 sysroot : & SysrootLock ,
217221 stateroot : & str ,
218- imgref : & ostree_container:: OstreeImageReference ,
219222 image : Box < LayeredImageState > ,
220- origin : & glib :: KeyFile ,
223+ spec : & HostSpec ,
221224) -> Result < ( ) > {
222225 let cancellable = gio:: Cancellable :: NONE ;
223226 let stateroot = Some ( stateroot) ;
224227 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+ }
225240 let _new_deployment = sysroot. stage_tree_with_options (
226241 stateroot,
227242 image. merge_commit . as_str ( ) ,
228- Some ( origin) ,
243+ Some ( & origin) ,
229244 merge_deployment. as_ref ( ) ,
230245 & Default :: default ( ) ,
231246 cancellable,
232247 ) ?;
233- println ! ( "Queued for next boot: {imgref}" ) ;
248+ if let Some ( imgref) = ostree_imgref. as_ref ( ) {
249+ println ! ( "Queued for next boot: {imgref}" ) ;
250+ }
234251 Ok ( ( ) )
235252}
236253
@@ -266,30 +283,30 @@ async fn prepare_for_write() -> Result<()> {
266283async fn upgrade ( opts : UpgradeOpts ) -> Result < ( ) > {
267284 prepare_for_write ( ) . await ?;
268285 let sysroot = & get_locked_sysroot ( ) . await ?;
269- let repo = & sysroot. repo ( ) ;
270286 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 ) {
281293 return Err ( anyhow:: anyhow!(
282294 "Booted deployment contains local rpm-ostree modifications; cannot upgrade via bootc"
283295 ) ) ;
284296 }
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 ( ) ) ;
289304 if opts. check {
290305 // pull the image manifest without the layers
291306 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 ?;
293310 match imp. prepare ( ) . await ? {
294311 PrepareResult :: AlreadyPresent ( c) => {
295312 println ! (
@@ -298,24 +315,27 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
298315 ) ;
299316 return Ok ( ( ) ) ;
300317 }
301- PrepareResult :: Ready ( p) => {
318+ PrepareResult :: Ready ( r) => {
319+ // TODO show a diff
302320 println ! (
303- "New manifest available for {}. Digest {}" ,
304- imgref , p . manifest_digest
321+ "New image available for {imgref }. Digest {}" ,
322+ r . manifest_digest
305323 ) ;
324+ // Note here we'll fall through to handling the --touch-if-changed below
306325 }
307326 }
308327 } 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+ }
314334 }
315335
316- stage ( sysroot, & osname, & imgref, fetched, & origin) . await ?;
336+ let osname = booted_deployment. osname ( ) ;
337+ stage ( sysroot, & osname, fetched, & host. spec ) . await ?;
317338 }
318-
319339 if let Some ( path) = opts. touch_if_changed {
320340 std:: fs:: write ( & path, "" ) . with_context ( || format ! ( "Writing {path}" ) ) ?;
321341 }
@@ -327,14 +347,14 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
327347#[ context( "Switching" ) ]
328348async fn switch ( opts : SwitchOpts ) -> Result < ( ) > {
329349 prepare_for_write ( ) . await ?;
330-
331350 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 ?;
337353 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 ( ) ;
338358
339359 let transport = ostree_container:: Transport :: try_from ( opts. transport . as_str ( ) ) ?;
340360 let imgref = ostree_container:: ImageReference {
@@ -349,30 +369,38 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
349369 SignatureSource :: ContainerPolicy
350370 } ;
351371 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+ }
352383
353384 let fetched = pull ( repo, & target, opts. quiet ) . await ?;
354385
355386 if !opts. retain {
356387 // 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+ }
365399 }
366400 }
367401
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 ?;
376404
377405 Ok ( ( ) )
378406}
0 commit comments