Skip to content

Commit 6391025

Browse files
committed
Explain unique replaces chain requirement in channel sort errors.
The "multiple channel head" error that can be returned from channel sorting has been a source of confusion for users. The error message now explains that a unique replacement chain is required in order to define the relative order between channel entries, and it also provides an abbreviated list of all replacement chains identified, in the form "head...tail" or "singleton". Signed-off-by: Ben Luddy <[email protected]>
1 parent f87b076 commit 6391025

File tree

2 files changed

+227
-81
lines changed

2 files changed

+227
-81
lines changed

pkg/controller/registry/resolver/resolver.go

+55-32
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"sort"
8+
"strings"
89

910
"github.com/blang/semver/v4"
1011
"github.com/sirupsen/logrus"
@@ -237,7 +238,7 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu
237238
if i != len(bundles) && bundles[i].bundle.ChannelName == lastChannel {
238239
continue
239240
}
240-
channel, err := r.sortChannel(bundles[lastIndex:i])
241+
channel, err := sortChannel(bundles[lastIndex:i])
241242
if err != nil {
242243
return nil, err
243244
}
@@ -610,7 +611,7 @@ func (r *SatResolver) sortBundles(bundles []*Operator) ([]*Operator, error) {
610611
return pi.Channel < pj.Channel
611612
})
612613
for channel := range partitionedBundles[catalog] {
613-
sorted, err := r.sortChannel(partitionedBundles[catalog][channel])
614+
sorted, err := sortChannel(partitionedBundles[catalog][channel])
614615
if err != nil {
615616
return nil, err
616617
}
@@ -626,13 +627,15 @@ func (r *SatResolver) sortBundles(bundles []*Operator) ([]*Operator, error) {
626627
return all, nil
627628
}
628629

629-
// sorts bundle in a channel by replaces
630-
func (r *SatResolver) sortChannel(bundles []*Operator) ([]*Operator, error) {
631-
if len(bundles) <= 1 {
630+
// Sorts bundle in a channel by replaces. All entries in the argument
631+
// are assumed to have the same Package and Channel.
632+
func sortChannel(bundles []*Operator) ([]*Operator, error) {
633+
if len(bundles) < 1 {
632634
return bundles, nil
633635
}
634636

635-
channel := []*Operator{}
637+
packageName := bundles[0].Package()
638+
channelName := bundles[0].Channel()
636639

637640
bundleLookup := map[string]*Operator{}
638641

@@ -660,44 +663,64 @@ func (r *SatResolver) sortChannel(bundles []*Operator) ([]*Operator, error) {
660663
}
661664
}
662665

663-
// a bundle without a replacement is a channel head, but if we find more than one of those something is weird
666+
// a bundle without a replacement is a channel head, but if we
667+
// find more than one of those something is weird
664668
headCandidates := []*Operator{}
665669
for _, b := range bundles {
666670
if _, ok := replacedBy[b]; !ok {
667671
headCandidates = append(headCandidates, b)
668672
}
669673
}
674+
if len(headCandidates) == 0 {
675+
return nil, fmt.Errorf("no channel heads (entries not replaced by another entry) found in channel %q of package %q", channelName, packageName)
676+
}
670677

671-
if len(headCandidates) > 1 {
672-
var names []string
673-
for _, v := range headCandidates {
674-
names = append(names, v.Identifier())
678+
var chains [][]*Operator
679+
for _, head := range headCandidates {
680+
var chain []*Operator
681+
visited := make(map[*Operator]struct{})
682+
current := head
683+
skip := false
684+
for {
685+
visited[current] = struct{}{}
686+
if !skip {
687+
chain = append(chain, current)
688+
}
689+
next, ok := replaces[current]
690+
if !ok {
691+
break
692+
}
693+
if _, ok := visited[next]; ok {
694+
return nil, fmt.Errorf("a cycle exists in the chain of replacement beginning with %q in channel %q of package %q", head.Identifier(), channelName, packageName)
695+
}
696+
if _, ok := skipped[current.Identifier()]; ok {
697+
skip = true
698+
}
699+
current = next
675700
}
676-
return nil, fmt.Errorf("found multiple channel heads: %v, please check the `replaces`/`skipRange` fields of the operator bundles", names)
677-
678-
} else if len(headCandidates) < 1 {
679-
return nil, fmt.Errorf("head of channel not found")
701+
chains = append(chains, chain)
680702
}
681703

682-
head := headCandidates[0]
683-
current := head
684-
skip := false
685-
for {
686-
if skip == false {
687-
channel = append(channel, current)
688-
}
689-
skip = false
690-
next, ok := replaces[current]
691-
if !ok {
692-
break
693-
}
694-
if _, ok := skipped[current.Identifier()]; ok {
695-
skip = true
704+
if len(chains) > 1 {
705+
schains := make([]string, len(chains))
706+
for i, chain := range chains {
707+
switch len(chain) {
708+
case 0:
709+
schains[i] = "[]" // Bug?
710+
case 1:
711+
schains[i] = chain[0].Identifier()
712+
default:
713+
schains[i] = fmt.Sprintf("%s...%s", chain[0].Identifier(), chain[len(chain)-1].Identifier())
714+
}
696715
}
697-
current = next
716+
return nil, fmt.Errorf("a unique replacement chain within a channel is required to determine the relative order between channel entries, but %d replacement chains were found in channel %q of package %q: %s", len(schains), channelName, packageName, strings.Join(schains, ", "))
698717
}
699718

700-
// TODO: do we care if the channel doesn't include every bundle in the input?
719+
if len(chains) == 0 {
720+
// Bug?
721+
return nil, fmt.Errorf("found no replacement chains in channel %q of package %q", channelName, packageName)
722+
}
701723

702-
return channel, nil
724+
// TODO: do we care if the channel doesn't include every bundle in the input?
725+
return chains[0], nil
703726
}

0 commit comments

Comments
 (0)