Skip to content

Commit a856ed0

Browse files
Merge pull request #160 from ankitathomas/bz1983673
Bug 1983673: Check for pruned bundles on add in replaces mode
2 parents fb92eba + a766f62 commit a856ed0

File tree

5 files changed

+650
-4
lines changed

5 files changed

+650
-4
lines changed

staging/operator-registry/cmd/opm/index/add.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ var (
1818
1919
This command will add the given set of bundle images (specified by the --bundles option) to an index image (provided by the --from-index option).
2020
21-
If multiple bundles are given with '--mode=replaces' (the default), bundles are added to the index by order of ascending (semver) version unless the update graph specified by replaces requires a different input order; e.g. 1.0.0 replaces 1.0.1 would result in [1.0.1, 1.0.0] instead of the [1.0.0, 1.0.1] normally expected of semver. However, for most cases (e.g. 1.0.1 replaces 1.0.0) the bundle with the highest version is used to set the default channel of the related package.
21+
If multiple bundles are given with '--mode=replaces' (the default), bundles are added to the index by order of ascending (semver) version unless the update graph specified by replaces requires a different input order; e.g. 1.0.0 replaces 1.0.1 would result in [1.0.1, 1.0.0] instead of the [1.0.0, 1.0.1] normally expected of semver. However, for most cases (e.g. 1.0.1 replaces 1.0.0) the bundle with the highest version is used to set the default channel of the related package.
22+
23+
Caveat: in replaces mode, the head of a channel is always the bundle with the highest semver. Any bundles upgrading from this channel-head will be pruned.
24+
An upgrade graph that looks like:
25+
0.1.1 -> 0.1.2 -> 0.1.2-1
26+
will be pruned on add to:
27+
0.1.1 -> 0.1.2
2228
`)
2329

2430
addExample = templates.Examples(`

staging/operator-registry/pkg/lib/registry/registry.go

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,16 @@ func populate(ctx context.Context, loader registry.Load, graphLoader registry.Gr
196196
}
197197

198198
populator := registry.NewDirectoryPopulator(loader, graphLoader, querier, unpackedImageMap, overwriteImageMap, overwrite)
199-
return populator.Populate(mode)
199+
if err := populator.Populate(mode); err != nil {
200+
return err
201+
}
202+
203+
for _, imgMap := range overwriteImageMap {
204+
for to, from := range imgMap {
205+
unpackedImageMap[to] = from
206+
}
207+
}
208+
return checkForBundles(ctx, querier.(*sqlite.SQLQuerier), graphLoader, unpackedImageMap)
200209
}
201210

202211
type DeleteFromRegistryRequest struct {
@@ -402,3 +411,97 @@ func checkForBundlePaths(querier registry.GRPCQuery, bundlePaths []string) ([]st
402411
}
403412
return found, missing, nil
404413
}
414+
415+
// packagesFromUnpackedRefs creates packages from a set of unpacked ref dirs without their upgrade edges.
416+
func packagesFromUnpackedRefs(bundles map[image.Reference]string) (map[string]registry.Package, error) {
417+
graph := map[string]registry.Package{}
418+
for to, from := range bundles {
419+
b, err := registry.NewImageInput(to, from)
420+
if err != nil {
421+
return nil, fmt.Errorf("failed to parse unpacked bundle image %s: %v", to, err)
422+
}
423+
v, err := b.Bundle.Version()
424+
if err != nil {
425+
return nil, fmt.Errorf("failed to parse version for %s (%s): %v", b.Bundle.Name, b.Bundle.BundleImage, err)
426+
}
427+
key := registry.BundleKey{
428+
CsvName: b.Bundle.Name,
429+
Version: v,
430+
BundlePath: b.Bundle.BundleImage,
431+
}
432+
if _, ok := graph[b.Bundle.Package]; !ok {
433+
graph[b.Bundle.Package] = registry.Package{
434+
Name: b.Bundle.Package,
435+
Channels: map[string]registry.Channel{},
436+
}
437+
}
438+
for _, c := range b.Bundle.Channels {
439+
if _, ok := graph[b.Bundle.Package].Channels[c]; !ok {
440+
graph[b.Bundle.Package].Channels[c] = registry.Channel{
441+
Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{},
442+
}
443+
}
444+
graph[b.Bundle.Package].Channels[c].Nodes[key] = nil
445+
}
446+
}
447+
448+
return graph, nil
449+
}
450+
451+
// replaces mode selects highest version as channel head and
452+
// prunes any bundles in the upgrade chain after the channel head.
453+
// check for the presence of all bundles after a replaces-mode add.
454+
func checkForBundles(ctx context.Context, q *sqlite.SQLQuerier, g registry.GraphLoader, bundles map[image.Reference]string) error {
455+
if len(bundles) == 0 {
456+
return nil
457+
}
458+
459+
required, err := packagesFromUnpackedRefs(bundles)
460+
if err != nil {
461+
return err
462+
}
463+
464+
var errs []error
465+
for _, pkg := range required {
466+
graph, err := g.Generate(pkg.Name)
467+
if err != nil {
468+
errs = append(errs, fmt.Errorf("unable to verify added bundles for package %s: %v", pkg.Name, err))
469+
continue
470+
}
471+
472+
for channel, missing := range pkg.Channels {
473+
// trace replaces chain for reachable bundles
474+
for next := []registry.BundleKey{graph.Channels[channel].Head}; len(next) > 0; next = next[1:] {
475+
delete(missing.Nodes, next[0])
476+
for edge := range graph.Channels[channel].Nodes[next[0]] {
477+
next = append(next, edge)
478+
}
479+
}
480+
481+
for bundle := range missing.Nodes {
482+
// check if bundle is deprecated. Bundles readded after deprecation should not be present in index and can be ignored.
483+
deprecated, err := isDeprecated(ctx, q, bundle)
484+
if err != nil {
485+
errs = append(errs, fmt.Errorf("could not validate pruned bundle %s (%s) as deprecated: %v", bundle.CsvName, bundle.BundlePath, err))
486+
}
487+
if !deprecated {
488+
errs = append(errs, fmt.Errorf("added bundle %s pruned from package %s, channel %s: this may be due to incorrect channel head (%s)", bundle.BundlePath, pkg.Name, channel, graph.Channels[channel].Head.CsvName))
489+
}
490+
}
491+
}
492+
}
493+
return utilerrors.NewAggregate(errs)
494+
}
495+
496+
func isDeprecated(ctx context.Context, q *sqlite.SQLQuerier, bundle registry.BundleKey) (bool, error) {
497+
props, err := q.GetPropertiesForBundle(ctx, bundle.CsvName, bundle.Version, bundle.BundlePath)
498+
if err != nil {
499+
return false, err
500+
}
501+
for _, prop := range props {
502+
if prop.Type == registry.DeprecatedType {
503+
return true, nil
504+
}
505+
}
506+
return false, nil
507+
}

0 commit comments

Comments
 (0)