Skip to content

Commit 9bae4d6

Browse files
committed
pkg/cvo: Compare Cincinnati data by digest when merging metadata
Fixing [1], where clusters installed with pullspecs like: registry.svc.ci.openshift.org/ocp/release@sha256:c7e8f18e8116356701bd23ae3a23fb9892dd5ea66c8300662ef30563d7104f39 would not pick up channel metadata that Cincinnati was associating with canonical pullspecs like: quay.io/openshift-release-dev/ocp-release@sha256:c7e8f18e8116356701bd23ae3a23fb9892dd5ea66c8300662ef30563d7104f39 I really wish source-repo "hints" were not part of pullspecs, and these images were purely content-addressable. But wishing doesn't make it true ;). The "Identical by tag" case is because even though we have no guarantee that a particular tag is stable, in any given moment there should only be one manifest backing a given pullspec, regardless of whether that pullspec is by-tag or by-digest. This is unlikely to matter in the wild, where Cincinnati will serve by-digest pullspecs. But it currently matters for CVO unit tests, where we rely on image/image:v1.0.0 matching for metadata population. And it matches the: merged.Image == availableUpdates.Current.Image and similar conditions we're replacing with equalDigest (so equalDigest will match what we used to match, and also the same-digest-different-repo cases we didn't match before). I'm not using the new equalDigest for some of our "is this change an update?" logic, because we want to go through "updates" on repository changes [2] to ensure that the release content is still available when accessed via the new pullspec (although the presence of ImageContentSourcePolicies may mean that even with a change to the repository portion of the pullspec, we still end up actually pulling the release from the same location we were initially using). [1]: https://bugzilla.redhat.com/show_bug.cgi?id=1879976 [2]: https://bugzilla.redhat.com/show_bug.cgi?id=1879963#c4
1 parent 4f4c8af commit 9bae4d6

File tree

3 files changed

+123
-3
lines changed

3 files changed

+123
-3
lines changed

pkg/cvo/cvo.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,11 +614,11 @@ func (optr *Operator) mergeReleaseMetadata(release configv1.Release) configv1.Re
614614
availableUpdates := optr.getAvailableUpdates()
615615
if availableUpdates != nil {
616616
var update *configv1.Release
617-
if merged.Image == availableUpdates.Current.Image {
617+
if equalDigest(merged.Image, availableUpdates.Current.Image) {
618618
update = &availableUpdates.Current
619619
} else {
620620
for _, u := range availableUpdates.Updates {
621-
if u.Image == merged.Image {
621+
if equalDigest(u.Image, merged.Image) {
622622
update = &u
623623
break
624624
}

pkg/cvo/sync_worker.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,11 +430,36 @@ func (w *SyncWorker) calculateNext(work *SyncWork) bool {
430430
}
431431

432432
// equalUpdate returns true if two updates are semantically equivalent.
433-
// It checks if the updates have have the same force and image values
433+
// It checks if the updates have have the same force and image values.
434+
//
435+
// We require complete pullspec equality, not just digest equality,
436+
// because we want to go through the usual update process even if it's
437+
// only the registry portion of the pullspec which has changed.
434438
func equalUpdate(a, b configv1.Update) bool {
435439
return a.Force == b.Force && a.Image == b.Image
436440
}
437441

442+
// equalDigest returns true if and only if the two pullspecs are
443+
// by-digest pullspecs and the digests are equal or the two pullspecs
444+
// are identical.
445+
func equalDigest(pullspecA string, pullspecB string) bool {
446+
if pullspecA == pullspecB {
447+
return true
448+
}
449+
digestA := splitDigest(pullspecA)
450+
return digestA != "" && digestA == splitDigest(pullspecB)
451+
}
452+
453+
// splitDigest returns the pullspec's digest, or an empty string if
454+
// the pullspec is not by-digest (e.g. it is by-tag, or implicit).
455+
func splitDigest(pullspec string) string {
456+
parts := strings.SplitN(pullspec, "@", 3)
457+
if len(parts) != 2 {
458+
return ""
459+
}
460+
return parts[1]
461+
}
462+
438463
// equalSyncWork returns true if a and b are equal.
439464
func equalSyncWork(a, b *SyncWork) bool {
440465
if a == b {

pkg/cvo/sync_worker_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,98 @@ func Test_runThrottledStatusNotifier(t *testing.T) {
189189
case <-time.After(100 * time.Millisecond):
190190
}
191191
}
192+
193+
func Test_equalDigest(t *testing.T) {
194+
for _, testCase := range []struct {
195+
name string
196+
pullspecA string
197+
pullspecB string
198+
expected bool
199+
}{
200+
{
201+
name: "both empty",
202+
pullspecA: "",
203+
pullspecB: "",
204+
expected: true,
205+
},
206+
{
207+
name: "A empty",
208+
pullspecA: "",
209+
pullspecB: "example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
210+
expected: false,
211+
},
212+
{
213+
name: "B empty",
214+
pullspecA: "example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
215+
pullspecB: "",
216+
expected: false,
217+
},
218+
{
219+
name: "A implicit tag",
220+
pullspecA: "example.com",
221+
pullspecB: "example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
222+
expected: false,
223+
},
224+
{
225+
name: "B implicit tag",
226+
pullspecA: "example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
227+
pullspecB: "example.com",
228+
expected: false,
229+
},
230+
{
231+
name: "A by tag",
232+
pullspecA: "example.com:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
233+
pullspecB: "example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
234+
expected: false,
235+
},
236+
{
237+
name: "B by tag",
238+
pullspecA: "example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
239+
pullspecB: "example.com:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
240+
expected: false,
241+
},
242+
{
243+
name: "identical by tag",
244+
pullspecA: "example.com:latest",
245+
pullspecB: "example.com:latest",
246+
expected: true,
247+
},
248+
{
249+
name: "different repositories, same tag",
250+
pullspecA: "a.example.com:latest",
251+
pullspecB: "b.example.com:latest",
252+
expected: false,
253+
},
254+
{
255+
name: "different repositories, same digest",
256+
pullspecA: "a.example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
257+
pullspecB: "b.example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
258+
expected: true,
259+
},
260+
{
261+
name: "same repository, different digests",
262+
pullspecA: "example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
263+
pullspecB: "example.com@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b",
264+
expected: false,
265+
},
266+
{
267+
name: "A empty repository, same digest",
268+
pullspecA: "@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
269+
pullspecB: "example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
270+
expected: true,
271+
},
272+
{
273+
name: "B empty repository, same digest",
274+
pullspecA: "example.com@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
275+
pullspecB: "@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
276+
expected: true,
277+
},
278+
} {
279+
t.Run(testCase.name, func(t *testing.T) {
280+
actual := equalDigest(testCase.pullspecA, testCase.pullspecB)
281+
if actual != testCase.expected {
282+
t.Fatalf("got %t, not the expected %t", actual, testCase.expected)
283+
}
284+
})
285+
}
286+
}

0 commit comments

Comments
 (0)