@@ -298,7 +298,7 @@ func delayedCrossMgrInit() {
298298 snapstate .DeviceCtx = DeviceCtx
299299 snapstate .RemodelingChange = RemodelingChange
300300 snapstate .SeedRefreshTasks = SeedRefreshTasks
301- snapstate .AppendSeedRefreshSetupTaskIDs = AppendSeedRefreshSetupTaskIDs
301+ snapstate .UpdateSeedRefreshChange = UpdateSeedRefreshChange
302302}
303303
304304// proxyStore returns the store assertion for the proxy store if one is set.
@@ -1763,26 +1763,41 @@ func removeRecoverySystemTask(st *state.State, label string) *state.Task {
17631763}
17641764
17651765// SeedRefreshTasks returns a [snapstate.SeedRefreshTaskSet] that carries the
1766- // tasks needed to refresh the seed managed by seed-refresh mode. The caller
1767- // must provide the tasks IDs that can be used by the seed creation tasks to
1768- // find the new snaps to include in the seed. Otherwise, already installed snaps
1769- // will be used to create the seed . Older seed-refresh systems are removed so
1766+ // tasks needed to refresh the seed managed by seed-refresh mode, plus the snap
1767+ // names selected for that seed refresh. The selected setup task IDs are written
1768+ // into the recovery-system setup payload so the new seed can consume the
1769+ // refreshed snaps and components . Older seed-refresh systems are removed so
17701770// that, after finalize records the new system, the two most recently created
17711771// seed-refresh systems remain tracked.
1772- func SeedRefreshTasks (st * state.State , snapSetupTasks , compSetupTasks []string ) (* snapstate.SeedRefreshTaskSet , error ) {
1772+ func SeedRefreshTasks (st * state.State , dctx snapstate.DeviceContext , candidates []snapstate.SeedRefreshCandidate ) (* snapstate.SeedRefreshTaskSet , map [string ]bool , error ) {
1773+ var snapsups , compsups []string
1774+ added := make (map [string ]bool , len (candidates ))
1775+ for _ , candidate := range candidates {
1776+ if ! seedRefreshIncludesSnap (dctx , candidate .InstanceName ) {
1777+ continue
1778+ }
1779+ added [candidate .InstanceName ] = true
1780+
1781+ snapsups = append (snapsups , candidate .SnapSetupTaskIDs ... )
1782+ compsups = append (compsups , candidate .ComponentSetupTaskIDs ... )
1783+ }
1784+ if len (added ) == 0 {
1785+ return nil , nil , nil
1786+ }
1787+
17731788 labelBase := timeNow ().Format ("20060102" )
17741789 label , err := pickRecoverySystemLabel (labelBase )
17751790 if err != nil {
1776- return nil , fmt .Errorf ("cannot select non-conflicting label for recovery system %q: %v" , labelBase , err )
1791+ return nil , nil , fmt .Errorf ("cannot select non-conflicting label for recovery system %q: %v" , labelBase , err )
17771792 }
17781793
1779- ts , err := createRecoverySystemTasks (st , label , snapSetupTasks , compSetupTasks , CreateRecoverySystemOptions {
1794+ ts , err := createRecoverySystemTasks (st , label , snapsups , compsups , CreateRecoverySystemOptions {
17801795 TestSystem : true ,
17811796 MarkDefault : true ,
17821797 SeedRefresh : true ,
17831798 })
17841799 if err != nil {
1785- return nil , err
1800+ return nil , nil , err
17861801 }
17871802
17881803 var create , finalize * state.Task
@@ -1796,12 +1811,12 @@ func SeedRefreshTasks(st *state.State, snapSetupTasks, compSetupTasks []string)
17961811 }
17971812
17981813 if create == nil || finalize == nil {
1799- return nil , errors .New ("internal error: expected create and finalize recovery system tasks" )
1814+ return nil , nil , errors .New ("internal error: expected create and finalize recovery system tasks" )
18001815 }
18011816
18021817 removeLabels , err := seedRefreshLabelsToRemove (st )
18031818 if err != nil {
1804- return nil , err
1819+ return nil , nil , err
18051820 }
18061821
18071822 removals := make ([]* state.Task , 0 , len (removeLabels ))
@@ -1815,23 +1830,99 @@ func SeedRefreshTasks(st *state.State, snapSetupTasks, compSetupTasks []string)
18151830 Create : create ,
18161831 Finalize : finalize ,
18171832 Remove : removals ,
1818- }, nil
1833+ }, added , nil
1834+ }
1835+
1836+ // UpdateSeedRefreshChange adds a late candidate to an existing seed-refresh
1837+ // change when the snap should participate in the refreshed seed. Returns nil if
1838+ // snap isn't part of the seed refresh, otherwise returns the seed refresh task
1839+ // set.
1840+ func UpdateSeedRefreshChange (chg * state.Change , dctx snapstate.DeviceContext , candidate snapstate.SeedRefreshCandidate ) (* snapstate.SeedRefreshTaskSet , error ) {
1841+ if ! seedRefreshIncludesSnap (dctx , candidate .InstanceName ) {
1842+ return nil , nil
1843+ }
1844+
1845+ seedTS , err := findSeedRefreshTasks (chg )
1846+ if err != nil {
1847+ return nil , err
1848+ }
1849+
1850+ if err := appendSeedRefreshCandidate (seedTS .Create , candidate .SnapSetupTaskIDs , candidate .ComponentSetupTaskIDs ); err != nil {
1851+ return nil , err
1852+ }
1853+
1854+ return seedTS , nil
18191855}
18201856
1821- // AppendSeedRefreshSetupTaskIDs appends unique setup task IDs to the
1822- // create-recovery-system task recovery-system-setup payload.
1823- func AppendSeedRefreshSetupTaskIDs (create * state.Task , snapSetupTask string , compSetupTasks []string ) error {
1857+ func appendSeedRefreshCandidate (create * state.Task , snapSetupTasks , compSetupTasks []string ) error {
18241858 setup , err := taskRecoverySystemSetup (create )
18251859 if err != nil {
18261860 return err
18271861 }
18281862
1829- setup .SnapSetupTasks = appendUnique (setup .SnapSetupTasks , snapSetupTask )
1863+ setup .SnapSetupTasks = appendUnique (setup .SnapSetupTasks , snapSetupTasks ... )
18301864 setup .ComponentSetupTasks = appendUnique (setup .ComponentSetupTasks , compSetupTasks ... )
18311865
18321866 return setTaskRecoverySystemSetup (create , setup )
18331867}
18341868
1869+ func seedRefreshIncludesSnap (dctx snapstate.DeviceContext , instanceName string ) bool {
1870+ // TODO:SEEDREFRESH: consider the intersections of snaps in the model and
1871+ // snaps currently present in the seed, not all snaps in the model.
1872+ if instanceName == "snapd" {
1873+ return true
1874+ }
1875+
1876+ for _ , sn := range dctx .Model ().AllSnaps () {
1877+ if sn .SnapName () == instanceName {
1878+ return true
1879+ }
1880+ }
1881+
1882+ return false
1883+ }
1884+
1885+ func findSeedRefreshTasks (chg * state.Change ) (* snapstate.SeedRefreshTaskSet , error ) {
1886+ var finalize * state.Task
1887+ var removals []* state.Task
1888+ for _ , t := range chg .Tasks () {
1889+ switch t .Kind () {
1890+ case "finalize-recovery-system" :
1891+ if t .Status ().Ready () {
1892+ continue
1893+ }
1894+ if finalize != nil {
1895+ return nil , errors .New ("internal error: found multiple pending seed finalization tasks in change" )
1896+ }
1897+ finalize = t
1898+ case "remove-recovery-system" :
1899+ if ! t .Status ().Ready () {
1900+ removals = append (removals , t )
1901+ }
1902+ }
1903+ }
1904+
1905+ if finalize == nil {
1906+ return nil , errors .New ("internal error: seed-refresh change is missing pending finalize-recovery-system task" )
1907+ }
1908+
1909+ var createID string
1910+ if err := finalize .Get ("recovery-system-setup-task" , & createID ); err != nil {
1911+ return nil , err
1912+ }
1913+
1914+ create := chg .State ().Task (createID )
1915+ if create == nil || create .Change ().ID () != chg .ID () || create .Kind () != "create-recovery-system" {
1916+ return nil , errors .New ("internal error: seed-refresh change is missing paired create-recovery-system task" )
1917+ }
1918+
1919+ return & snapstate.SeedRefreshTaskSet {
1920+ Create : create ,
1921+ Finalize : finalize ,
1922+ Remove : removals ,
1923+ }, nil
1924+ }
1925+
18351926func appendUnique (slice []string , additions ... string ) []string {
18361927 seen := make (map [string ]bool , len (slice ))
18371928 for _ , id := range slice {
0 commit comments