Skip to content

Commit d0132ee

Browse files
author
Charlie Egan
authored
store: Improve conflicting root error message (#7808)
Fixes #7806 ``` { "errors": [ { "message": "detected overlapping roots in bundle manifest with: [b2.tar.gz b1.tar.gz]" } ] } { "errors": [ { "message": "bundles [b1.tar.gz, b2.tar.gz] have overlapping roots and cannot be activated simultaneously because bundle(s) [b1.tar.gz] specify empty root paths ('') which overlap with any other bundle root" } ] } ``` Signed-off-by: Charlie Egan <[email protected]>
1 parent 4647660 commit d0132ee

File tree

3 files changed

+115
-55
lines changed

3 files changed

+115
-55
lines changed

v1/bundle/store.go

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"fmt"
1313
"maps"
1414
"path/filepath"
15+
"slices"
16+
"sort"
1517
"strings"
1618
"sync"
1719

@@ -1059,32 +1061,40 @@ func lookup(path storage.Path, data map[string]any) (any, bool) {
10591061
return value, ok
10601062
}
10611063

1062-
func hasRootsOverlap(ctx context.Context, store storage.Store, txn storage.Transaction, bundles map[string]*Bundle) error {
1063-
collisions := map[string][]string{}
1064-
allBundles, err := ReadBundleNamesFromStore(ctx, store, txn)
1064+
func hasRootsOverlap(ctx context.Context, store storage.Store, txn storage.Transaction, newBundles map[string]*Bundle) error {
1065+
storeBundles, err := ReadBundleNamesFromStore(ctx, store, txn)
10651066
if suppressNotFound(err) != nil {
10661067
return err
10671068
}
10681069

10691070
allRoots := map[string][]string{}
1071+
bundlesWithEmptyRoots := map[string]bool{}
10701072

10711073
// Build a map of roots for existing bundles already in the system
1072-
for _, name := range allBundles {
1074+
for _, name := range storeBundles {
10731075
roots, err := ReadBundleRootsFromStore(ctx, store, txn, name)
10741076
if suppressNotFound(err) != nil {
10751077
return err
10761078
}
10771079
allRoots[name] = roots
1080+
if slices.Contains(roots, "") {
1081+
bundlesWithEmptyRoots[name] = true
1082+
}
10781083
}
10791084

10801085
// Add in any bundles that are being activated, overwrite existing roots
10811086
// with new ones where bundles are in both groups.
1082-
for name, bundle := range bundles {
1087+
for name, bundle := range newBundles {
10831088
allRoots[name] = *bundle.Manifest.Roots
1089+
if slices.Contains(*bundle.Manifest.Roots, "") {
1090+
bundlesWithEmptyRoots[name] = true
1091+
}
10841092
}
10851093

10861094
// Now check for each new bundle if it conflicts with any of the others
1087-
for name, bundle := range bundles {
1095+
collidingBundles := map[string]bool{}
1096+
conflictSet := map[string]bool{}
1097+
for name, bundle := range newBundles {
10881098
for otherBundle, otherRoots := range allRoots {
10891099
if name == otherBundle {
10901100
// Skip the current bundle being checked
@@ -1094,22 +1104,41 @@ func hasRootsOverlap(ctx context.Context, store storage.Store, txn storage.Trans
10941104
// Compare the "new" roots with other existing (or a different bundles new roots)
10951105
for _, newRoot := range *bundle.Manifest.Roots {
10961106
for _, otherRoot := range otherRoots {
1097-
if RootPathsOverlap(newRoot, otherRoot) {
1098-
collisions[otherBundle] = append(collisions[otherBundle], newRoot)
1107+
if !RootPathsOverlap(newRoot, otherRoot) {
1108+
continue
1109+
}
1110+
1111+
collidingBundles[name] = true
1112+
collidingBundles[otherBundle] = true
1113+
1114+
// Different message required if the roots are same
1115+
if newRoot == otherRoot {
1116+
conflictSet[fmt.Sprintf("root %s is in multiple bundles", newRoot)] = true
1117+
} else {
1118+
paths := []string{newRoot, otherRoot}
1119+
sort.Strings(paths)
1120+
conflictSet[fmt.Sprintf("%s overlaps %s", paths[0], paths[1])] = true
10991121
}
11001122
}
11011123
}
11021124
}
11031125
}
11041126

1105-
if len(collisions) > 0 {
1106-
var bundleNames []string
1107-
for name := range collisions {
1108-
bundleNames = append(bundleNames, name)
1109-
}
1110-
return fmt.Errorf("detected overlapping roots in bundle manifest with: %s", bundleNames)
1127+
if len(collidingBundles) == 0 {
1128+
return nil
11111129
}
1112-
return nil
1130+
1131+
bundleNames := strings.Join(util.KeysSorted(collidingBundles), ", ")
1132+
1133+
if len(bundlesWithEmptyRoots) > 0 {
1134+
return fmt.Errorf(
1135+
"bundles [%s] have overlapping roots and cannot be activated simultaneously because bundle(s) [%s] specify empty root paths ('') which overlap with any other bundle root",
1136+
bundleNames,
1137+
strings.Join(util.KeysSorted(bundlesWithEmptyRoots), ", "),
1138+
)
1139+
}
1140+
1141+
return fmt.Errorf("detected overlapping roots in manifests for these bundles: [%s] (%s)", bundleNames, strings.Join(util.KeysSorted(conflictSet), ", "))
11131142
}
11141143

11151144
func applyPatches(ctx context.Context, store storage.Store, txn storage.Transaction, patches []PatchOperation) error {

v1/bundle/store_test.go

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6621,52 +6621,78 @@ func TestHasRootsOverlap(t *testing.T) {
66216621
ctx := context.Background()
66226622

66236623
cases := []struct {
6624-
note string
6625-
storeRoots map[string]*[]string
6626-
bundleRoots map[string]*[]string
6627-
overlaps bool
6624+
note string
6625+
storeRoots map[string]*[]string
6626+
newBundleRoots map[string]*[]string
6627+
expectedError string
66286628
}{
66296629
{
6630-
note: "no overlap with existing roots",
6631-
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6632-
bundleRoots: map[string]*[]string{"bundle2": {"c"}},
6633-
overlaps: false,
6630+
note: "no overlap between store and new bundles",
6631+
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6632+
newBundleRoots: map[string]*[]string{"bundle2": {"c"}},
6633+
},
6634+
{
6635+
note: "no overlap between store and multiple new bundles",
6636+
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6637+
newBundleRoots: map[string]*[]string{"bundle2": {"c"}, "bundle3": {"d"}},
6638+
},
6639+
{
6640+
note: "no overlap with empty store",
6641+
storeRoots: map[string]*[]string{},
6642+
newBundleRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6643+
},
6644+
{
6645+
note: "no overlap between multiple new bundles with empty store",
6646+
storeRoots: map[string]*[]string{},
6647+
newBundleRoots: map[string]*[]string{"bundle1": {"a", "b"}, "bundle2": {"c"}},
6648+
},
6649+
{
6650+
note: "overlap between multiple new bundles with empty store",
6651+
storeRoots: map[string]*[]string{},
6652+
newBundleRoots: map[string]*[]string{"bundle1": {"a", "b"}, "bundle2": {"a", "c"}},
6653+
expectedError: "detected overlapping roots in manifests for these bundles: [bundle1, bundle2] (root a is in multiple bundles)",
66346654
},
66356655
{
6636-
note: "no overlap with existing roots multiple bundles",
6637-
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6638-
bundleRoots: map[string]*[]string{"bundle2": {"c"}, "bundle3": {"d"}},
6639-
overlaps: false,
6656+
note: "overlap between store and new bundle",
6657+
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6658+
newBundleRoots: map[string]*[]string{"bundle2": {"c", "a"}},
6659+
expectedError: "detected overlapping roots in manifests for these bundles: [bundle1, bundle2] (root a is in multiple bundles)",
66406660
},
66416661
{
6642-
note: "no overlap no existing roots",
6643-
storeRoots: map[string]*[]string{},
6644-
bundleRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6645-
overlaps: false,
6662+
note: "overlap between store and multiple new bundles",
6663+
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6664+
newBundleRoots: map[string]*[]string{"bundle2": {"c", "a"}, "bundle3": {"a"}},
6665+
expectedError: "detected overlapping roots in manifests for these bundles: [bundle1, bundle2, bundle3] (root a is in multiple bundles)",
66466666
},
66476667
{
6648-
note: "no overlap without existing roots multiple bundles",
6649-
storeRoots: map[string]*[]string{},
6650-
bundleRoots: map[string]*[]string{"bundle1": {"a", "b"}, "bundle2": {"c"}},
6651-
overlaps: false,
6668+
note: "overlap between store bundle and new empty root bundle",
6669+
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6670+
newBundleRoots: map[string]*[]string{"bundle2": {""}},
6671+
expectedError: "bundles [bundle1, bundle2] have overlapping roots and cannot be activated simultaneously because bundle(s) [bundle2] specify empty root paths ('') which overlap with any other bundle root",
66526672
},
66536673
{
6654-
note: "overlap without existing roots multiple bundles",
6655-
storeRoots: map[string]*[]string{},
6656-
bundleRoots: map[string]*[]string{"bundle1": {"a", "b"}, "bundle2": {"a", "c"}},
6657-
overlaps: true,
6674+
note: "overlap between multiple new empty root bundles",
6675+
storeRoots: map[string]*[]string{},
6676+
newBundleRoots: map[string]*[]string{"bundle1": {""}, "bundle2": {""}},
6677+
expectedError: "bundles [bundle1, bundle2] have overlapping roots and cannot be activated simultaneously because bundle(s) [bundle1, bundle2] specify empty root paths ('') which overlap with any other bundle root",
66586678
},
66596679
{
6660-
note: "overlap with existing roots",
6661-
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6662-
bundleRoots: map[string]*[]string{"bundle2": {"c", "a"}},
6663-
overlaps: true,
6680+
note: "overlap between new empty root and new regular root bundles",
6681+
storeRoots: map[string]*[]string{},
6682+
newBundleRoots: map[string]*[]string{"bundle1": {"a"}, "bundle2": {""}},
6683+
expectedError: "bundles [bundle1, bundle2] have overlapping roots and cannot be activated simultaneously because bundle(s) [bundle2] specify empty root paths ('') which overlap with any other bundle root",
66646684
},
66656685
{
6666-
note: "overlap with existing roots multiple bundles",
6667-
storeRoots: map[string]*[]string{"bundle1": {"a", "b"}},
6668-
bundleRoots: map[string]*[]string{"bundle2": {"c", "a"}, "bundle3": {"a"}},
6669-
overlaps: true,
6686+
note: "overlap between nested paths",
6687+
storeRoots: map[string]*[]string{},
6688+
newBundleRoots: map[string]*[]string{"bundle1": {"a"}, "bundle2": {"a/b"}},
6689+
expectedError: "detected overlapping roots in manifests for these bundles: [bundle1, bundle2] (a overlaps a/b)",
6690+
},
6691+
{
6692+
note: "overlap between store nested path and new bundle path",
6693+
storeRoots: map[string]*[]string{"bundle1": {"a/b"}},
6694+
newBundleRoots: map[string]*[]string{"bundle2": {"a"}},
6695+
expectedError: "detected overlapping roots in manifests for these bundles: [bundle1, bundle2] (a overlaps a/b)",
66706696
},
66716697
}
66726698

@@ -6683,7 +6709,7 @@ func TestHasRootsOverlap(t *testing.T) {
66836709
}
66846710

66856711
bundles := map[string]*Bundle{}
6686-
for name, roots := range tc.bundleRoots {
6712+
for name, roots := range tc.newBundleRoots {
66876713
bundles[name] = &Bundle{
66886714
Manifest: Manifest{
66896715
Roots: roots,
@@ -6692,10 +6718,15 @@ func TestHasRootsOverlap(t *testing.T) {
66926718
}
66936719

66946720
err := hasRootsOverlap(ctx, mockStore, txn, bundles)
6695-
if !tc.overlaps && err != nil {
6696-
t.Fatalf("unepected error: %s", err)
6697-
} else if tc.overlaps && (err == nil || !strings.Contains(err.Error(), "detected overlapping roots in bundle manifest")) {
6698-
t.Fatalf("expected overlapping roots error, got: %s", err)
6721+
if tc.expectedError != "" {
6722+
if err == nil {
6723+
t.Fatalf("expected error %q, got nil", tc.expectedError)
6724+
}
6725+
if err.Error() != tc.expectedError {
6726+
t.Fatalf("expected error message %q, got %q", tc.expectedError, err.Error())
6727+
}
6728+
} else if err != nil {
6729+
t.Fatalf("unexpected error: %s", err)
66996730
}
67006731

67016732
err = mockStore.Commit(ctx, txn)

v1/plugins/bundle/plugin_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3282,7 +3282,7 @@ func ensureBundleOverlapStatus(t *testing.T, p *Plugin, bundleNames []string, ex
32823282
t.Fatalf("expected bundle %s to be in an error state", name)
32833283
} else if !expectedErrs[i] && hasErr {
32843284
t.Fatalf("unexpected error state for bundle %s", name)
3285-
} else if hasErr && expectedErrs[i] && !strings.Contains(p.status[name].Message, "detected overlapping roots") {
3285+
} else if hasErr && expectedErrs[i] && !strings.Contains(p.status[name].Message, "overlapping roots") {
32863286
t.Fatalf("expected bundle overlap error for bundle %s, got: %s", name, p.status[name].Message)
32873287
}
32883288
}
@@ -7209,8 +7209,8 @@ result := true`,
72097209
if status.Code != errCode {
72107210
t.Fatalf("Expected status code to be %s, found %s", errCode, status.Code)
72117211
}
7212-
if !strings.Contains(status.Message, "detected overlapping") {
7213-
t.Fatalf(`Expected status message to contain "detected overlapping roots", found %s`, status.Message)
7212+
if !strings.Contains(status.Message, "specify empty root paths") {
7213+
t.Fatalf(`Expected status message to contain "specify empty root paths", found %s`, status.Message)
72147214
}
72157215
}
72167216

0 commit comments

Comments
 (0)