Skip to content

Commit d828173

Browse files
committed
fix: only reload state after collectors when digest changed
Compare the digest before and after collectors run. If it changed, writes happened and the local checksum may be stale, so reload from the server. If unchanged, no writes occurred and reload is skipped. Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>
1 parent 2baee00 commit d828173

2 files changed

Lines changed: 23 additions & 12 deletions

File tree

pkg/attestation/crafter/crafter.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,21 @@ func (c *Crafter) RunCollectors(ctx context.Context, attestationID string, casBa
147147
return
148148
}
149149

150+
digestBeforeCollectors := c.CraftingState.UpdateCheckSum
151+
150152
for _, collector := range c.collectors {
151153
if err := collector.Collect(ctx, c, attestationID, casBackend); err != nil {
152154
c.Logger.Warn().Err(err).Str("collector", collector.ID()).Msg("collector failed")
153155
}
154156
}
155157

156-
// Reload state to sync the local digest with the server after collectors modified it.
157-
// This is needed for backward compatibility with servers that don't return digests in Save responses.
158-
if err := c.LoadCraftingState(ctx, attestationID); err != nil {
159-
c.Logger.Warn().Err(err).Msg("failed to reload crafting state after running collectors")
158+
// Reload state if collectors modified it and the local digest may be stale.
159+
// When the digest changed, writes happened but we can't be sure the checksum
160+
// reflects the true server-side state (e.g. old servers don't return digests).
161+
if c.CraftingState.UpdateCheckSum != digestBeforeCollectors {
162+
if err := c.LoadCraftingState(ctx, attestationID); err != nil {
163+
c.Logger.Warn().Err(err).Msg("failed to reload crafting state after running collectors")
164+
}
160165
}
161166
}
162167

pkg/attestation/crafter/runcollectors_test.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,29 @@ func newSuccessCollector(t *testing.T, id string) *craftermocks.Collector {
5757
}
5858

5959
func TestRunCollectors(t *testing.T) {
60-
t.Run("loads state before and after running collectors", func(t *testing.T) {
60+
t.Run("reloads state after collectors when digest changes", func(t *testing.T) {
6161
sm := craftermocks.NewStateManager(t)
6262
setupReadExpectation(sm, "digest-1")
6363
sm.On("Info", mock.Anything, mock.Anything).Return("mock://run-1")
6464

65-
c1 := newSuccessCollector(t, "c1")
66-
c2 := newSuccessCollector(t, "c2")
65+
// Collector modifies the digest (simulates a Write that updates UpdateCheckSum)
66+
c1 := craftermocks.NewCollector(t)
67+
c1.On("ID").Maybe().Return("c1")
68+
c1.On("Collect", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
69+
Run(func(args mock.Arguments) {
70+
cr := args.Get(1).(*crafter.Crafter)
71+
cr.CraftingState.UpdateCheckSum = "digest-after-write"
72+
}).Return(nil)
6773

6874
cr, err := crafter.NewCrafter(sm, nil)
6975
require.NoError(t, err)
70-
cr.RegisterCollectors(c1, c2)
76+
cr.RegisterCollectors(c1)
7177

7278
cr.RunCollectors(context.Background(), "run-1", nil)
7379

74-
// Read before the loop + Read after to sync digest
80+
// Read before + Read after (digest changed, so reload triggered)
7581
sm.AssertNumberOfCalls(t, "Read", 2)
7682
c1.AssertCalled(t, "Collect", mock.Anything, mock.Anything, "run-1", mock.Anything)
77-
c2.AssertCalled(t, "Collect", mock.Anything, mock.Anything, "run-1", mock.Anything)
7883
})
7984

8085
t.Run("collector failure does not stop other collectors", func(t *testing.T) {
@@ -99,7 +104,7 @@ func TestRunCollectors(t *testing.T) {
99104
c2.AssertCalled(t, "Collect", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
100105
})
101106

102-
t.Run("no collectors reloads state before and after", func(t *testing.T) {
107+
t.Run("no collectors skips reload when digest unchanged", func(t *testing.T) {
103108
sm := craftermocks.NewStateManager(t)
104109
setupReadExpectation(sm, "d")
105110
sm.On("Info", mock.Anything, mock.Anything).Return("mock://run-1")
@@ -109,7 +114,8 @@ func TestRunCollectors(t *testing.T) {
109114

110115
cr.RunCollectors(context.Background(), "run-1", nil)
111116

112-
sm.AssertNumberOfCalls(t, "Read", 2)
117+
// Only the initial Read, no reload since digest didn't change
118+
sm.AssertNumberOfCalls(t, "Read", 1)
113119
})
114120

115121
t.Run("state load failure aborts before running collectors", func(t *testing.T) {

0 commit comments

Comments
 (0)