Skip to content

Commit 2da6fd0

Browse files
authored
o/confdbstate: check for ephemeral change when missing save-view hook on commit (#16889)
Although we error early if we can tell that a write affects ephemeral data but no save-view hook is present, a change-view hook may have written to an ephemeral path after that initial check so we need to check again before committing. Signed-off-by: Miguel Pires <miguel.pires@canonical.com>
1 parent 5bcef5d commit 2da6fd0

3 files changed

Lines changed: 105 additions & 1 deletion

File tree

overlord/confdbstate/confdbmgr.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,46 @@ func (m *ConfdbManager) doCommitTransaction(t *state.Task, _ *tomb.Tomb) (err er
105105
}
106106
schema := confdbAssert.Schema().DatabagSchema
107107

108+
hasSaveViewHook := false
109+
for _, task := range t.Change().Tasks() {
110+
if task.Kind() != "run-hook" {
111+
continue
112+
}
113+
114+
var hooksup hookstate.HookSetup
115+
err := task.Get("hook-setup", &hooksup)
116+
if err != nil {
117+
return fmt.Errorf(`internal error: cannot get "hook-setup" from run-hook task: %w`, err)
118+
}
119+
120+
if strings.HasPrefix(hooksup.Hook, "save-view-") {
121+
hasSaveViewHook = true
122+
break
123+
}
124+
}
125+
126+
// we error early if a write may affect ephemeral data but no save-view hook
127+
// is present. However, a change-view hook may have written to an ephemeral
128+
// path after that so we have to check again
129+
if !hasSaveViewHook {
130+
var viewName string
131+
err = t.Get("view", &viewName)
132+
if err != nil {
133+
return fmt.Errorf(`internal error: cannot get "view" from task: %w`, err)
134+
}
135+
136+
view := confdbAssert.Schema().View(viewName)
137+
paths := tx.AlteredPaths()
138+
mightAffectEph, err := view.WriteAffectsEphemeral(paths)
139+
if err != nil {
140+
return fmt.Errorf("cannot commit transaction: cannot check for ephemeral paths: %v", err)
141+
}
142+
143+
if mightAffectEph {
144+
return fmt.Errorf("cannot commit transaction: write may affect ephemeral data but no save-view hook is present")
145+
}
146+
}
147+
108148
return tx.Commit(st, schema)
109149
}
110150

overlord/confdbstate/confdbmgr_test.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package confdbstate_test
2020

2121
import (
22+
"context"
2223
"errors"
2324
"strings"
2425
"time"
@@ -33,6 +34,7 @@ import (
3334
"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
3435
"github.com/snapcore/snapd/overlord/state"
3536
"github.com/snapcore/snapd/testutil"
37+
"gopkg.in/tomb.v2"
3638

3739
. "gopkg.in/check.v1"
3840
)
@@ -442,6 +444,7 @@ func (s *confdbTestSuite) TestCommitTransaction(c *C) {
442444
c.Assert(err, IsNil)
443445

444446
setTransaction(t, tx)
447+
t.Set("view", "setup-wifi")
445448

446449
s.state.Unlock()
447450
err = s.o.Settle(testutil.HostScaledTimeout(5 * time.Second))
@@ -518,6 +521,7 @@ func (s *confdbTestSuite) TestClearTransactionOnError(c *C) {
518521
err = tx.Set(parsePath(c, "foo"), "bar")
519522
c.Assert(err, IsNil)
520523
setTransaction(commitTask, tx)
524+
commitTask.Set("view", "setup-wifi")
521525

522526
// add this transaction to the state
523527
err = confdbstate.SetWriteTransaction(s.state, s.devAccID, "network", commitTask.ID())
@@ -531,10 +535,68 @@ func (s *confdbTestSuite) TestClearTransactionOnError(c *C) {
531535
c.Assert(chg.Status(), Equals, state.ErrorStatus)
532536
c.Assert(commitTask.Status(), Equals, state.ErrorStatus)
533537
c.Assert(clearTask.Status(), Equals, state.UndoneStatus)
534-
c.Assert(strings.Join(commitTask.Log(), "\n"), Matches, ".*ERROR cannot accept top level element: map contains unexpected key \"foo\"")
538+
c.Assert(strings.Join(commitTask.Log(), "\n"), Matches, ".*ERROR cannot commit transaction: cannot check for ephemeral paths: cannot check if write affects ephemeral data: cannot use \"foo\" as key in map")
535539

536540
// no ongoing confdb transaction
537541
var ongoingTxs map[string]*confdbstate.ConfdbTransactions
538542
err = s.state.Get("confdb-ongoing-txs", &ongoingTxs)
539543
c.Assert(err, testutil.ErrorIs, &state.NoStateError{})
540544
}
545+
546+
func (s *confdbTestSuite) TestCommitTransactionEphemeralCheckWithoutSaveViewHooks(c *C) {
547+
s.state.Lock()
548+
defer s.state.Unlock()
549+
550+
// the custodian has a change-view hook but no save-view
551+
custodians := map[string]confdbHooks{"custodian-snap": changeView}
552+
s.setupConfdbScenario(c, custodians, nil)
553+
554+
// mock a change-view hook that writes to ephemeral data
555+
restore := hookstate.MockRunHook(func(ctx *hookstate.Context, _ *tomb.Tomb) ([]byte, error) {
556+
t, _ := ctx.Task()
557+
ctx.State().Lock()
558+
defer ctx.State().Unlock()
559+
560+
var hooksup *hookstate.HookSetup
561+
err := t.Get("hook-setup", &hooksup)
562+
if err != nil {
563+
return nil, err
564+
}
565+
c.Assert(strings.HasPrefix(hooksup.Hook, "change-view-"), Equals, true)
566+
567+
tx, _, saveChanges, err := confdbstate.GetStoredTransaction(t)
568+
if err != nil {
569+
return nil, err
570+
}
571+
572+
err = tx.Set(parsePath(c, "wifi.eph"), "ephemeral-from-hook")
573+
if err != nil {
574+
return nil, err
575+
}
576+
saveChanges()
577+
578+
return nil, nil
579+
})
580+
defer restore()
581+
582+
view, err := confdbstate.GetView(s.state, s.devAccID, "network", "setup-wifi")
583+
c.Assert(err, IsNil)
584+
585+
chgID, err := confdbstate.WriteConfdb(context.Background(), s.state, view, map[string]any{"ssid": "my-wifi"})
586+
c.Assert(err, IsNil)
587+
588+
chg := s.state.Change(chgID)
589+
c.Assert(chg, NotNil)
590+
591+
s.state.Unlock()
592+
err = s.o.Settle(testutil.HostScaledTimeout(5 * time.Second))
593+
s.state.Lock()
594+
c.Assert(err, IsNil)
595+
596+
// commit fails because change-view hook wrote ephemeral data but no save-view hooks exist
597+
c.Assert(chg.Status(), Equals, state.ErrorStatus)
598+
599+
commitTask := findTask(chg, "commit-confdb-tx")
600+
c.Assert(commitTask, NotNil)
601+
c.Assert(strings.Join(commitTask.Log(), "\n"), Matches, `.*ERROR cannot commit transaction: write may affect ephemeral data but no save-view hook is present.*`)
602+
}

overlord/confdbstate/confdbstate.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,8 @@ func createChangeConfdbTasks(st *state.State, tx *Transaction, view *confdb.View
525525
// commit after custodians save ephemeral data
526526
commitTask := st.NewTask("commit-confdb-tx", fmt.Sprintf("Commit changes to confdb (%s)", view.ID()))
527527
commitTask.Set("confdb-transaction", tx)
528+
commitTask.Set("view", view.Name)
529+
528530
// link all previous tasks to the commit task that carries the transaction
529531
for _, t := range ts.Tasks() {
530532
t.Set("tx-task", commitTask.ID())

0 commit comments

Comments
 (0)