many: make ignore-validation sticky and send the flag with refresh requests #4111

Merged
merged 1 commit into from Nov 2, 2017
Jump to file or symbol
Failed to load files and symbols.
+353 −52
Split
View
@@ -339,16 +339,17 @@ func mapLocal(about aboutSnap) *client.Snap {
Version: localSnap.Version,
Channel: localSnap.Channel,
TrackingChannel: snapst.Channel,
- Confinement: string(localSnap.Confinement),
- DevMode: snapst.DevMode,
- TryMode: snapst.TryMode,
- JailMode: snapst.JailMode,
- Private: localSnap.Private,
- Apps: apps,
- Broken: localSnap.Broken,
- Contact: localSnap.Contact,
- Title: localSnap.Title(),
- License: localSnap.License,
+ // TODO: send ignore-validation
+ Confinement: string(localSnap.Confinement),
+ DevMode: snapst.DevMode,
+ TryMode: snapst.TryMode,
+ JailMode: snapst.JailMode,
+ Private: localSnap.Private,
+ Apps: apps,
+ Broken: localSnap.Broken,
+ Contact: localSnap.Contact,
+ Title: localSnap.Title(),
+ License: localSnap.License,
}
return result
@@ -175,8 +175,8 @@ func (e *refreshControlError) Error() string {
return fmt.Sprintf("refresh control errors:%s", strings.Join(l, "\n - "))
}
-// ValidateRefreshes validates the refresh candidate revisions represented by the snapInfos, looking for the needed refresh control validation assertions, it returns a validated subset in validated and a summary error if not all candidates validated.
-func ValidateRefreshes(s *state.State, snapInfos []*snap.Info, userID int) (validated []*snap.Info, err error) {
+// ValidateRefreshes validates the refresh candidate revisions represented by the snapInfos, looking for the needed refresh control validation assertions, it returns a validated subset in validated and a summary error if not all candidates validated. ignoreValidation is a set of snap-ids that should not be gated.
+func ValidateRefreshes(s *state.State, snapInfos []*snap.Info, ignoreValidation map[string]bool, userID int) (validated []*snap.Info, err error) {
// maps gated snap-ids to gating snap-ids
controlled := make(map[string][]string)
// maps gating snap-ids to their snap names
@@ -210,7 +210,9 @@ func ValidateRefreshes(s *state.State, snapInfos []*snap.Info, userID int) (vali
}
gatingNames[gatingID] = decl.SnapName()
for _, gatedID := range control {
- controlled[gatedID] = append(controlled[gatedID], gatingID)
+ if !ignoreValidation[gatedID] {
+ controlled[gatedID] = append(controlled[gatedID], gatingID)
+ }
}
}
@@ -708,7 +708,7 @@ func (s *assertMgrSuite) TestValidateRefreshesNothing(c *C) {
s.state.Lock()
defer s.state.Unlock()
- validated, err := assertstate.ValidateRefreshes(s.state, nil, 0)
+ validated, err := assertstate.ValidateRefreshes(s.state, nil, nil, 0)
c.Assert(err, IsNil)
c.Check(validated, HasLen, 0)
}
@@ -735,7 +735,7 @@ func (s *assertMgrSuite) TestValidateRefreshesNoControl(c *C) {
SideInfo: snap.SideInfo{RealName: "foo", SnapID: "foo-id", Revision: snap.R(9)},
}
- validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, 0)
+ validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, nil, 0)
c.Assert(err, IsNil)
c.Check(validated, DeepEquals, []*snap.Info{fooRefresh})
}
@@ -764,11 +764,40 @@ func (s *assertMgrSuite) TestValidateRefreshesMissingValidation(c *C) {
SideInfo: snap.SideInfo{RealName: "foo", SnapID: "foo-id", Revision: snap.R(9)},
}
- validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, 0)
+ validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, nil, 0)
c.Assert(err, ErrorMatches, `cannot refresh "foo" to revision 9: no validation by "bar"`)
c.Check(validated, HasLen, 0)
}
+func (s *assertMgrSuite) TestValidateRefreshesMissingValidationButIgnore(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapDeclFoo := s.snapDecl(c, "foo", nil)
+ snapDeclBar := s.snapDecl(c, "bar", map[string]interface{}{
+ "refresh-control": []interface{}{"foo-id"},
+ })
+ s.stateFromDecl(snapDeclFoo, snap.R(7))
+ s.stateFromDecl(snapDeclBar, snap.R(3))
+
+ err := assertstate.Add(s.state, s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+ err = assertstate.Add(s.state, s.dev1Acct)
+ c.Assert(err, IsNil)
+ err = assertstate.Add(s.state, snapDeclFoo)
+ c.Assert(err, IsNil)
+ err = assertstate.Add(s.state, snapDeclBar)
+ c.Assert(err, IsNil)
+
+ fooRefresh := &snap.Info{
+ SideInfo: snap.SideInfo{RealName: "foo", SnapID: "foo-id", Revision: snap.R(9)},
+ }
+
+ validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, map[string]bool{"foo-id": true}, 0)
+ c.Assert(err, IsNil)
+ c.Check(validated, DeepEquals, []*snap.Info{fooRefresh})
+}
+
func (s *assertMgrSuite) TestValidateRefreshesValidationOK(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -832,7 +861,7 @@ func (s *assertMgrSuite) TestValidateRefreshesValidationOK(c *C) {
SideInfo: snap.SideInfo{RealName: "foo", SnapID: "foo-id", Revision: snap.R(9)},
}
- validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, 0)
+ validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, nil, 0)
c.Assert(err, IsNil)
c.Check(validated, DeepEquals, []*snap.Info{fooRefresh})
}
@@ -901,7 +930,7 @@ func (s *assertMgrSuite) TestValidateRefreshesRevokedValidation(c *C) {
SideInfo: snap.SideInfo{RealName: "foo", SnapID: "foo-id", Revision: snap.R(9)},
}
- validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, 0)
+ validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, nil, 0)
c.Assert(err, ErrorMatches, `(?s).*cannot refresh "foo" to revision 9: validation by "baz" \(id "baz-id"\) revoked.*`)
c.Check(validated, HasLen, 0)
}
@@ -94,6 +94,7 @@ type fakeStore struct {
storetest.Store
downloads []fakeDownload
+ refreshRevnos map[string]snap.Revision
fakeBackend *fakeSnappyBackend
fakeCurrentProgress int
fakeTotalProgress int
@@ -177,6 +178,9 @@ func (f *fakeStore) LookupRefresh(cand *store.RefreshCandidate, user *auth.UserS
}
revno := snap.R(11)
+ if r := f.refreshRevnos[cand.SnapID]; !r.Unset() {
+ revno = r
+ }
confinement := snap.StrictConfinement
switch cand.Channel {
case "channel-for-7":
@@ -62,7 +62,6 @@ func (f Flags) DevModeAllowed() bool {
// ForSnapSetup returns a copy of the Flags with the flags that we don't need in SnapSetup set to false (so they're not serialized)
func (f Flags) ForSnapSetup() Flags {
- f.IgnoreValidation = false
f.SkipConfigure = false
return f
}
@@ -643,6 +643,8 @@ func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) error {
if snapsup.Channel != "" {
snapst.Channel = snapsup.Channel
}
+ oldIgnoreValidation := snapst.IgnoreValidation
+ snapst.IgnoreValidation = snapsup.IgnoreValidation
oldTryMode := snapst.TryMode
snapst.TryMode = snapsup.TryMode
oldDevMode := snapst.DevMode
@@ -694,6 +696,7 @@ func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) error {
t.Set("old-devmode", oldDevMode)
t.Set("old-jailmode", oldJailMode)
t.Set("old-classic", oldClassic)
+ t.Set("old-ignore-validation", oldIgnoreValidation)
t.Set("old-channel", oldChannel)
t.Set("old-current", oldCurrent)
t.Set("old-candidate-index", oldCandidateIndex)
@@ -738,6 +741,11 @@ func (m *SnapManager) undoLinkSnap(t *state.Task, _ *tomb.Tomb) error {
if err != nil {
return err
}
+ var oldIgnoreValidation bool
+ err = t.Get("old-ignore-validation", &oldIgnoreValidation)
+ if err != nil && err != state.ErrNoState {
+ return err
+ }
var oldTryMode bool
err = t.Get("old-trymode", &oldTryMode)
if err != nil {
@@ -792,6 +800,7 @@ func (m *SnapManager) undoLinkSnap(t *state.Task, _ *tomb.Tomb) error {
snapst.Current = oldCurrent
snapst.Active = false
snapst.Channel = oldChannel
+ snapst.IgnoreValidation = oldIgnoreValidation
snapst.TryMode = oldTryMode
snapst.DevMode = oldDevMode
snapst.JailMode = oldJailMode
@@ -542,20 +542,21 @@ func InstallMany(st *state.State, names []string, userID int) ([]string, []*stat
// RefreshCandidates gets a list of candidates for update
// Note that the state must be locked by the caller.
func RefreshCandidates(st *state.State, user *auth.UserState) ([]*snap.Info, error) {
- updates, _, err := refreshCandidates(st, nil, user)
+ updates, _, _, err := refreshCandidates(st, nil, user)
return updates, err
}
-func refreshCandidates(st *state.State, names []string, user *auth.UserState) ([]*snap.Info, map[string]*SnapState, error) {
+func refreshCandidates(st *state.State, names []string, user *auth.UserState) ([]*snap.Info, map[string]*SnapState, map[string]bool, error) {
snapStates, err := All(st)
if err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
sort.Strings(names)
stateByID := make(map[string]*SnapState, len(snapStates))
candidatesInfo := make([]*store.RefreshCandidate, 0, len(snapStates))
+ ignoreValidation := make(map[string]bool)
for _, snapst := range snapStates {
if len(names) == 0 && (snapst.TryMode || snapst.DevMode) {
// no auto-refresh for trymode nor devmode
@@ -588,17 +589,21 @@ func refreshCandidates(st *state.State, names []string, user *auth.UserState) ([
// get confinement preference from the snapstate
candidateInfo := &store.RefreshCandidate{
// the desired channel (not info.Channel!)
- Channel: snapst.Channel,
- SnapID: snapInfo.SnapID,
- Revision: snapInfo.Revision,
- Epoch: snapInfo.Epoch,
+ Channel: snapst.Channel,
+ SnapID: snapInfo.SnapID,
+ Revision: snapInfo.Revision,
+ Epoch: snapInfo.Epoch,
+ IgnoreValidation: snapst.IgnoreValidation,
}
if len(names) == 0 {
candidateInfo.Block = snapst.Block()
}
candidatesInfo = append(candidatesInfo, candidateInfo)
+ if snapst.IgnoreValidation {
+ ignoreValidation[snapInfo.SnapID] = true
+ }
}
theStore := Store(st)
@@ -607,14 +612,14 @@ func refreshCandidates(st *state.State, names []string, user *auth.UserState) ([
updates, err := theStore.ListRefresh(candidatesInfo, user)
st.Lock()
if err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
- return updates, stateByID, nil
+ return updates, stateByID, ignoreValidation, nil
}
// ValidateRefreshes allows to hook validation into the handling of refresh candidates.
-var ValidateRefreshes func(st *state.State, refreshes []*snap.Info, userID int) (validated []*snap.Info, err error)
+var ValidateRefreshes func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int) (validated []*snap.Info, err error)
// UpdateMany updates everything from the given list of names that the
// store says is updateable. If the list is empty, update everything.
@@ -625,13 +630,13 @@ func UpdateMany(st *state.State, names []string, userID int) ([]string, []*state
return nil, nil, err
}
- updates, stateByID, err := refreshCandidates(st, names, user)
+ updates, stateByID, ignoreValidation, err := refreshCandidates(st, names, user)
if err != nil {
return nil, nil, err
}
if ValidateRefreshes != nil && len(updates) != 0 {
- updates, err = ValidateRefreshes(st, updates, userID)
+ updates, err = ValidateRefreshes(st, updates, ignoreValidation, userID)
if err != nil {
// not doing "refresh all" report the error
if len(names) != 0 {
@@ -930,6 +935,7 @@ func Update(st *state.State, name, channel string, revision snap.Revision, userI
// see if we need to update the channel
if infoErr == store.ErrNoUpdateAvailable && snapst.Channel != channel {
+ // TODO: do we want to treat ignore-validation similarly?
snapsup := &SnapSetup{
SideInfo: snapst.CurrentSideInfo(),
// update the tracked channel
@@ -963,15 +969,15 @@ func Update(st *state.State, name, channel string, revision snap.Revision, userI
func infoForUpdate(st *state.State, snapst *SnapState, name, channel string, revision snap.Revision, userID int, flags Flags) (*snap.Info, error) {
if revision.Unset() {
// good ol' refresh
- info, err := updateInfo(st, snapst, channel, userID)
+ info, err := updateInfo(st, snapst, channel, flags.IgnoreValidation, userID)
if err != nil {
return nil, err
}
if err := validateInfoAndFlags(info, snapst, flags); err != nil {
return nil, err
}
if ValidateRefreshes != nil && !flags.IgnoreValidation {
- _, err := ValidateRefreshes(st, []*snap.Info{info}, userID)
+ _, err := ValidateRefreshes(st, []*snap.Info{info}, nil, userID)
if err != nil {
return nil, err
}
Oops, something went wrong.