Skip to content

Commit aff2fbb

Browse files
authored
fix(cli): resolve attestation init digest mismatch with old servers (#2999)
Signed-off-by: Javier Rodriguez <javier@chainloop.dev>
1 parent 11bfcd6 commit aff2fbb

2 files changed

Lines changed: 34 additions & 15 deletions

File tree

pkg/attestation/crafter/crafter.go

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

150-
digestBeforeCollectors := c.CraftingState.UpdateCheckSum
151-
152150
for _, collector := range c.collectors {
153151
if err := collector.Collect(ctx, c, attestationID, casBackend); err != nil {
154152
c.Logger.Warn().Err(err).Str("collector", collector.ID()).Msg("collector failed")
155153
}
156154
}
157155

158-
// NOTE: workaround for old servers that don't return digests in Save responses.
159-
// not returning digest makes digests stale, and state save failing with conflict errors
160-
// https://github.com/chainloop-dev/chainloop/issues/2908
161-
// this condition will not apply to new servers that return digests in Save responses.
162-
if c.CraftingState.UpdateCheckSum != digestBeforeCollectors {
163-
if err := c.LoadCraftingState(ctx, attestationID); err != nil {
164-
c.Logger.Warn().Err(err).Msg("failed to reload crafting state after running collectors")
165-
}
156+
// Always reload state after collectors to ensure the digest is in sync with the server.
157+
// Collectors persist changes via Write(), but old servers don't return the new digest
158+
// in Save responses, leaving the client-side UpdateCheckSum stale.
159+
// Reloading via Read() fetches the authoritative digest regardless of server version.
160+
if err := c.LoadCraftingState(ctx, attestationID); err != nil {
161+
c.Logger.Warn().Err(err).Msg("failed to reload crafting state after running collectors")
166162
}
167163
}
168164

pkg/attestation/crafter/runcollectors_test.go

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

5959
func TestRunCollectors(t *testing.T) {
60-
t.Run("reloads state after collectors when digest changes", func(t *testing.T) {
60+
t.Run("always reloads state after collectors", 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")
@@ -77,7 +77,7 @@ func TestRunCollectors(t *testing.T) {
7777

7878
cr.RunCollectors(context.Background(), "run-1", nil)
7979

80-
// Read before + Read after (digest changed, so reload triggered)
80+
// Read before collectors + unconditional Read after collectors
8181
sm.AssertNumberOfCalls(t, "Read", 2)
8282
c1.AssertCalled(t, "Collect", mock.Anything, mock.Anything, "run-1", mock.Anything)
8383
})
@@ -102,9 +102,10 @@ func TestRunCollectors(t *testing.T) {
102102

103103
c1.AssertCalled(t, "Collect", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
104104
c2.AssertCalled(t, "Collect", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
105+
sm.AssertNumberOfCalls(t, "Read", 2)
105106
})
106107

107-
t.Run("no collectors skips reload when digest unchanged", func(t *testing.T) {
108+
t.Run("reloads state even with no collectors", func(t *testing.T) {
108109
sm := craftermocks.NewStateManager(t)
109110
setupReadExpectation(sm, "d")
110111
sm.On("Info", mock.Anything, mock.Anything).Return("mock://run-1")
@@ -114,8 +115,30 @@ func TestRunCollectors(t *testing.T) {
114115

115116
cr.RunCollectors(context.Background(), "run-1", nil)
116117

117-
// Only the initial Read, no reload since digest didn't change
118-
sm.AssertNumberOfCalls(t, "Read", 1)
118+
// Read before + unconditional Read after, even with no collectors
119+
sm.AssertNumberOfCalls(t, "Read", 2)
120+
})
121+
122+
t.Run("reloads state when digest is stale (old server)", func(t *testing.T) {
123+
sm := craftermocks.NewStateManager(t)
124+
setupReadExpectation(sm, "digest-1")
125+
sm.On("Info", mock.Anything, mock.Anything).Return("mock://run-1")
126+
127+
// Collector persists state via Write but the old server doesn't return
128+
// a new digest, so UpdateCheckSum stays unchanged (stale).
129+
c1 := craftermocks.NewCollector(t)
130+
c1.On("ID").Maybe().Return("c1")
131+
c1.On("Collect", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
132+
133+
cr, err := crafter.NewCrafter(sm, nil)
134+
require.NoError(t, err)
135+
cr.RegisterCollectors(c1)
136+
137+
cr.RunCollectors(context.Background(), "run-1", nil)
138+
139+
// Unconditional reload ensures the stale digest is refreshed
140+
sm.AssertNumberOfCalls(t, "Read", 2)
141+
c1.AssertCalled(t, "Collect", mock.Anything, mock.Anything, "run-1", mock.Anything)
119142
})
120143

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

0 commit comments

Comments
 (0)