@@ -426,15 +426,102 @@ func isBroken(st *state.State, snapName string) (bool, error) {
426426 return false , nil
427427}
428428
429+ func cloneConnState (connState * schema.ConnState ) * schema.ConnState {
430+ clone := * connState
431+
432+ cloneAttrs := func (attrs map [string ]any ) map [string ]any {
433+ if attrs == nil {
434+ return nil
435+ }
436+ return utils .CopyAttributes (attrs )
437+ }
438+
439+ clone .StaticPlugAttrs = cloneAttrs (connState .StaticPlugAttrs )
440+ clone .DynamicPlugAttrs = cloneAttrs (connState .DynamicPlugAttrs )
441+ clone .StaticSlotAttrs = cloneAttrs (connState .StaticSlotAttrs )
442+ clone .DynamicSlotAttrs = cloneAttrs (connState .DynamicSlotAttrs )
443+
444+ return & clone
445+ }
446+
447+ // snapshotChangedConnectionsForUndo records original states for persisted
448+ // connections that setup-profiles changed or dropped so undo can restore them,
449+ // if needed.
450+ func snapshotChangedConnectionsForUndo (task * state.Task , instanceName string , changedConns map [string ]* schema.ConnState ) error {
451+ if len (changedConns ) == 0 {
452+ return nil
453+ }
454+
455+ // if this isn't the setup-profiles task that is going to handle the undo,
456+ // then we don't need to keep track of these on the task
457+ if ! shouldUndoSetupProfiles (task , instanceName ) {
458+ return nil
459+ }
460+
461+ var connectionSnapshot map [string ]* schema.ConnState
462+ err := task .Get ("changed-or-dropped-connection-snapshot" , & connectionSnapshot )
463+ if err != nil && ! errors .Is (err , state .ErrNoState ) {
464+ return err
465+ }
466+ if connectionSnapshot == nil {
467+ connectionSnapshot = make (map [string ]* schema.ConnState )
468+ }
469+
470+ for connID , connState := range changedConns {
471+ if connectionSnapshot [connID ] != nil {
472+ // a setup-profiles task can be retried after saving the connection
473+ // states and unlocking for backend setup. keep the first snapshot.
474+ continue
475+ }
476+ connectionSnapshot [connID ] = connState
477+ }
478+
479+ task .Set ("changed-or-dropped-connection-snapshot" , connectionSnapshot )
480+
481+ return nil
482+ }
483+
484+ // restoreConnectionsForSetupProfiles restores connection states saved by
485+ // snapshotChangedConnectionsForUndo on a setup-profiles task.
486+ func restoreConnectionsForSetupProfiles (task * state.Task ) error {
487+ var connectionSnapshot map [string ]* schema.ConnState
488+ err := task .Get ("changed-or-dropped-connection-snapshot" , & connectionSnapshot )
489+ if errors .Is (err , state .ErrNoState ) {
490+ return nil
491+ }
492+ if err != nil {
493+ return err
494+ }
495+
496+ st := task .State ()
497+
498+ conns , err := getConns (st )
499+ if err != nil {
500+ return err
501+ }
502+
503+ for connID , connState := range connectionSnapshot {
504+ conns [connID ] = connState
505+ }
506+ setConns (st , conns )
507+
508+ return nil
509+ }
510+
429511// reloadConnections reloads connections stored in the state in the repository.
430512// Using non-empty snapName the operation can be scoped to connections
431513// affecting a given snap.
432514//
433- // The return value is the list of affected snap names and their connection IDs.
434- func (m * InterfaceManager ) reloadConnections (snapName string ) (reloadedConnectionIDs []string , err error ) {
515+ // The return value is the list of reloaded connection IDs, plus the original
516+ // connection states whose persisted state was changed or dropped.
517+ func (m * InterfaceManager ) reloadConnections (snapName string ) (
518+ reloadedConnectionIDs []string ,
519+ changedOrDroppedConns map [string ]* schema.ConnState ,
520+ err error ,
521+ ) {
435522 conns , err := getConns (m .state )
436523 if err != nil {
437- return nil , err
524+ return nil , nil , err
438525 }
439526
440527 var policyChecker interfaces.PolicyFunc
@@ -445,21 +532,22 @@ func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectio
445532 if errors .Is (err , state .ErrNoState ) {
446533 // everything else is a noop, as no model means no connections
447534 // to reload
448- return nil , nil
535+ return nil , nil , nil
449536 } else if err != nil {
450- return nil , err
537+ return nil , nil , err
451538 }
452539 autoChecker , err = newAutoConnectChecker (m .state , m .repo , deviceCtx )
453540 if err != nil {
454- return nil , err
541+ return nil , nil , err
455542 }
456543
457544 connChecker , err = newConnectChecker (m .state , deviceCtx )
458545 if err != nil {
459- return nil , err
546+ return nil , nil , err
460547 }
461548
462549 connStateChanged := false
550+ changedOrDroppedConns = make (map [string ]* schema.ConnState )
463551
464552 var reloadedConnections []string
465553ConnsLoop:
@@ -473,7 +561,7 @@ ConnsLoop:
473561 }
474562 connRef , err := interfaces .ParseConnRef (connId )
475563 if err != nil {
476- return nil , err
564+ return nil , nil , err
477565 }
478566 // Apply filtering, this allows us to reload only a subset of
479567 // connections (and similarly, refresh the static attributes of only a
@@ -497,13 +585,14 @@ ConnsLoop:
497585 for _ , snapName := range []string {connRef .PlugRef .Snap , connRef .SlotRef .Snap } {
498586 broken , err := isBroken (m .state , snapName )
499587 if err != nil {
500- return nil , err
588+ return nil , nil , err
501589 }
502590 if broken {
503591 logger .Noticef ("Snap %q is broken, ignored by reloadConnections" , snapName )
504592 continue ConnsLoop
505593 }
506594 }
595+ changedOrDroppedConns [connId ] = cloneConnState (connState )
507596 delete (conns , connId )
508597 connStateChanged = true
509598 }
@@ -540,11 +629,11 @@ ConnsLoop:
540629
541630 plugAppSet , err := interfaces .NewSnapAppSet (plugInfo .Snap , nil )
542631 if err != nil {
543- return nil , err
632+ return nil , nil , err
544633 }
545634 slotAppSet , err := interfaces .NewSnapAppSet (slotInfo .Snap , nil )
546635 if err != nil {
547- return nil , err
636+ return nil , nil , err
548637 }
549638
550639 cplug := interfaces .NewConnectedPlug (plugInfo , plugAppSet , newStaticPlugAttrs , connState .DynamicPlugAttrs )
@@ -568,6 +657,7 @@ ConnsLoop:
568657 reloadedConnections = append (reloadedConnections , connId )
569658
570659 if updateStaticAttrs {
660+ changedOrDroppedConns [connId ] = cloneConnState (connState )
571661 connState .StaticPlugAttrs = staticPlugAttrs
572662 connState .StaticSlotAttrs = staticSlotAttrs
573663 connStateChanged = true
@@ -578,7 +668,7 @@ ConnsLoop:
578668 setConns (m .state , conns )
579669 }
580670
581- return reloadedConnections , nil
671+ return reloadedConnections , changedOrDroppedConns , nil
582672}
583673
584674// removeConnections disconnects all connections of the snap in the repo. It should only be used if the snap
0 commit comments