Skip to content

Commit

Permalink
o/snapstate: move creating update actions into a new helper function
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewphelpsj committed Jul 9, 2024
1 parent a066209 commit f05437f
Showing 1 changed file with 118 additions and 101 deletions.
219 changes: 118 additions & 101 deletions overlord/snapstate/storehelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,8 +600,6 @@ func storeUpdatePlan(
}
}

actionsByUserID := make(map[int][]*store.SnapAction)

// create a closure that will lazily load the enforced validation sets if
// any of the targets require them
var vsets *snapasserts.ValidationSets
Expand All @@ -619,33 +617,136 @@ func storeUpdatePlan(
return vsets, nil
}

// this map keeps track of snaps that already have a local revision that
var fallbackID int
// normalize fallback user
if !user.HasStoreAuth() {
user = nil
} else {
fallbackID = user.ID
}

// hasLocalRevision keeps track of snaps that already have a local revision
// matches the requested revision. there are two distinct cases here:
//
// * the snap might have been requested to be updated but didn't get
// updated, either because we detected that the requested/required revision
// is already installed, or the store reported that there was no update
// available.
//
// * the snap has a local copy of the revision (that was previously
// * we have a local copy of the revision (that was previously installed,
// installed, but isn't right now) that is the same as the requested
// revision
//
// in either case, we need to keep track of these, since we still might need
// to change the channel, cohort key, or validation set enforcement.
hasLocalRevision := make(map[*SnapState]RevisionOptions)
actionsByUserID, hasLocalRevision, current, err := collectCurrentSnapsAndActions(st, allSnaps, updates, plan.requested, opts, enforcedSets, fallbackID)
if err != nil {
return updatePlan{}, err
}

var fallbackID int
// normalize fallback user
if !user.HasStoreAuth() {
user = nil
} else {
fallbackID = user.ID
// create actions to refresh (install, from the store's perspective) snaps
// that were installed locally
amendActionsByUserID, err := installActionsForAmend(st, updates, opts, enforcedSets, fallbackID)
if err != nil {
return updatePlan{}, err
}

for id, actions := range amendActionsByUserID {
actionsByUserID[id] = append(actionsByUserID[id], actions...)
}

sars, noStoreUpdates, err := sendActionsByUserID(ctx, st, actionsByUserID, current, refreshOpts, opts)
if err != nil {
return updatePlan{}, err
}

for _, name := range noStoreUpdates {
hasLocalRevision[allSnaps[name]] = updates[name].RevOpts
}

for _, sar := range sars {
up, ok := updates[sar.InstanceName()]
if !ok {
return updatePlan{}, fmt.Errorf("unsolicited snap action result: %q", sar.InstanceName())
}

snapst, ok := allSnaps[sar.InstanceName()]
if !ok {
return updatePlan{}, fmt.Errorf("internal error: snap %q not found", sar.InstanceName())
}

// TODO: handle components here

plan.targets = append(plan.targets, target{
info: sar.Info,
snapst: *snapst,
setup: SnapSetup{
DownloadInfo: &sar.DownloadInfo,
Channel: up.RevOpts.Channel,
CohortKey: up.RevOpts.CohortKey,
},
components: nil,
})
}

// consider snaps that already have a local copy of the revision that we are
// trying to install, skipping a trip to the store
for snapst, revOpts := range hasLocalRevision {
var si *snap.SideInfo
if !revOpts.Revision.Unset() {
si = snapst.Sequence.Revisions[snapst.LastIndex(revOpts.Revision)].Snap
} else {
si = snapst.CurrentSideInfo()
}

info, err := readInfo(snapst.InstanceName(), si, errorOnBroken)
if err != nil {
return updatePlan{}, err
}

// TODO: handle components here

// make sure that we switch the current channel of the snap that we're
// switching to
info.Channel = revOpts.Channel

plan.targets = append(plan.targets, target{
info: info,
snapst: *snapst,
setup: SnapSetup{
Channel: revOpts.Channel,
CohortKey: revOpts.CohortKey,
SnapPath: info.MountFile(),

// if the caller specified a revision, then we always run
// through the entire update process. this enables something
// like "snap refresh --revision=n", where revision n is already
// installed
AlwaysUpdate: !revOpts.Revision.Unset(),
},
components: nil,
})
}

return plan, nil
}

func collectCurrentSnapsAndActions(
st *state.State,
allSnaps map[string]*SnapState,
updates map[string]StoreUpdate,
requested []string,
opts Options,
enforcedSets func() (*snapasserts.ValidationSets, error),
fallbackID int,
) (map[int][]*store.SnapAction, map[*SnapState]RevisionOptions, []*store.CurrentSnap, error) {
hasLocalRevision := make(map[*SnapState]RevisionOptions)
actionsByUserID := make(map[int][]*store.SnapAction)
refreshAll := len(requested) == 0

addCand := func(installed *store.CurrentSnap, snapst *SnapState) error {
// no auto-refresh for devmode
if plan.refreshAll() && snapst.DevMode {
if refreshAll && snapst.DevMode {
return nil
}

Expand Down Expand Up @@ -708,7 +809,7 @@ func storeUpdatePlan(
}

// only enforce refresh block if we are refreshing everything
if plan.refreshAll() {
if refreshAll {
installed.Block = snapst.Block()
}

Expand All @@ -723,103 +824,19 @@ func storeUpdatePlan(

// TODO: is this right? why do we only pass in the requested names here?
// what about when we are refreshing all snaps?
holds, err := SnapHolds(st, plan.requested)
holds, err := SnapHolds(st, requested)
if err != nil {
return updatePlan{}, err
return nil, nil, nil, err
}

// determine current snaps and create actions for each snap that needs to
// be refreshed
current, err := collectCurrentSnaps(allSnaps, holds, addCand)
if err != nil {
return updatePlan{}, err
}

// create actions to refresh (install, from the store's perspective) snaps
// that were installed locally
amendActionsByUserID, err := installActionsForAmend(st, updates, opts, enforcedSets, fallbackID)
if err != nil {
return updatePlan{}, err
}

for id, actions := range amendActionsByUserID {
actionsByUserID[id] = append(actionsByUserID[id], actions...)
}

sars, noStoreUpdates, err := sendActionsByUserID(ctx, st, actionsByUserID, current, refreshOpts, opts)
if err != nil {
return updatePlan{}, err
return nil, nil, nil, err
}

for _, name := range noStoreUpdates {
hasLocalRevision[allSnaps[name]] = updates[name].RevOpts
}

for _, sar := range sars {
up, ok := updates[sar.InstanceName()]
if !ok {
return updatePlan{}, fmt.Errorf("unsolicited snap action result: %q", sar.InstanceName())
}

snapst, ok := allSnaps[sar.InstanceName()]
if !ok {
return updatePlan{}, fmt.Errorf("internal error: snap %q not found", sar.InstanceName())
}

// TODO: handle components here

plan.targets = append(plan.targets, target{
info: sar.Info,
snapst: *snapst,
setup: SnapSetup{
DownloadInfo: &sar.DownloadInfo,
Channel: up.RevOpts.Channel,
CohortKey: up.RevOpts.CohortKey,
},
components: nil,
})
}

// consider snaps that already have a local copy of the revision that we are
// trying to install, skipping a trip to the store
for snapst, revOpts := range hasLocalRevision {
var si *snap.SideInfo
if !revOpts.Revision.Unset() {
si = snapst.Sequence.Revisions[snapst.LastIndex(revOpts.Revision)].Snap
} else {
si = snapst.CurrentSideInfo()
}

info, err := readInfo(snapst.InstanceName(), si, errorOnBroken)
if err != nil {
return updatePlan{}, err
}

// TODO: handle components here

// make sure that we switch the current channel of the snap that we're
// switching to
info.Channel = revOpts.Channel

plan.targets = append(plan.targets, target{
info: info,
snapst: *snapst,
setup: SnapSetup{
Channel: revOpts.Channel,
CohortKey: revOpts.CohortKey,
SnapPath: info.MountFile(),

// if the caller specified a revision, then we always run
// through the entire update process. this enables something
// like "snap refresh --revision=n", where revision n is already
// installed
AlwaysUpdate: !revOpts.Revision.Unset(),
},
components: nil,
})
}

return plan, nil
return actionsByUserID, hasLocalRevision, current, nil
}

func installActionsForAmend(st *state.State, updates map[string]StoreUpdate, opts Options, enforcedSets func() (*snapasserts.ValidationSets, error), fallbackID int) (map[int][]*store.SnapAction, error) {
Expand Down

0 comments on commit f05437f

Please sign in to comment.