@@ -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,97 @@ 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.
1838+ func UpdateSeedRefreshChange (chg * state.Change , dctx snapstate.DeviceContext , candidate snapstate.SeedRefreshCandidate ) (* snapstate.SeedRefreshTaskSet , error ) {
1839+ if ! seedRefreshIncludesSnap (dctx , candidate .InstanceName ) {
1840+ return nil , nil
1841+ }
1842+
1843+ seedTS , err := findSeedRefreshTasks (chg )
1844+ if err != nil {
1845+ return nil , err
1846+ }
1847+
1848+ if err := appendSeedRefreshCandidate (seedTS .Create , candidate .SnapSetupTaskIDs , candidate .ComponentSetupTaskIDs ); err != nil {
1849+ return nil , err
1850+ }
1851+
1852+ return seedTS , nil
18191853}
18201854
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 {
1855+ func appendSeedRefreshCandidate (create * state.Task , snapSetupTasks , compSetupTasks []string ) error {
18241856 setup , err := taskRecoverySystemSetup (create )
18251857 if err != nil {
18261858 return err
18271859 }
18281860
1829- setup .SnapSetupTasks = appendUnique (setup .SnapSetupTasks , snapSetupTask )
1861+ setup .SnapSetupTasks = appendUnique (setup .SnapSetupTasks , snapSetupTasks ... )
18301862 setup .ComponentSetupTasks = appendUnique (setup .ComponentSetupTasks , compSetupTasks ... )
18311863
18321864 return setTaskRecoverySystemSetup (create , setup )
18331865}
18341866
1867+ func seedRefreshIncludesSnap (dctx snapstate.DeviceContext , instanceName string ) bool {
1868+ // TODO:SEEDREFRESH: consider the intersections of snaps in the model and
1869+ // snaps currently present in the seed, not all snaps in the model.
1870+ if instanceName == "snapd" {
1871+ return true
1872+ }
1873+
1874+ for _ , sn := range dctx .Model ().AllSnaps () {
1875+ if sn .SnapName () == instanceName {
1876+ return true
1877+ }
1878+ }
1879+
1880+ return false
1881+ }
1882+
1883+ func findSeedRefreshTasks (chg * state.Change ) (* snapstate.SeedRefreshTaskSet , error ) {
1884+ var finalize * state.Task
1885+ var removals []* state.Task
1886+ for _ , t := range chg .Tasks () {
1887+ switch t .Kind () {
1888+ case "finalize-recovery-system" :
1889+ if t .Status ().Ready () {
1890+ continue
1891+ }
1892+ if finalize != nil {
1893+ return nil , errors .New ("internal error: found multiple pending seed finalization tasks in change" )
1894+ }
1895+ finalize = t
1896+ case "remove-recovery-system" :
1897+ if ! t .Status ().Ready () {
1898+ removals = append (removals , t )
1899+ }
1900+ }
1901+ }
1902+
1903+ if finalize == nil {
1904+ return nil , errors .New ("internal error: seed-refresh change is missing pending finalize-recovery-system task" )
1905+ }
1906+
1907+ var createID string
1908+ if err := finalize .Get ("recovery-system-setup-task" , & createID ); err != nil {
1909+ return nil , err
1910+ }
1911+
1912+ create := chg .State ().Task (createID )
1913+ if create == nil || create .Change ().ID () != chg .ID () || create .Kind () != "create-recovery-system" {
1914+ return nil , errors .New ("internal error: seed-refresh change is missing paired create-recovery-system task" )
1915+ }
1916+
1917+ return & snapstate.SeedRefreshTaskSet {
1918+ Create : create ,
1919+ Finalize : finalize ,
1920+ Remove : removals ,
1921+ }, nil
1922+ }
1923+
18351924func appendUnique (slice []string , additions ... string ) []string {
18361925 seen := make (map [string ]bool , len (slice ))
18371926 for _ , id := range slice {
0 commit comments