github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/olm/requirements.go (about)

     1  package olm
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/coreos/go-semver/semver"
    11  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    12  	listersv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
    13  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
    14  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/internal/alongside"
    15  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
    16  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
    17  	"github.com/sirupsen/logrus"
    18  	rbacv1 "k8s.io/api/rbac/v1"
    19  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/apimachinery/pkg/labels"
    22  )
    23  
    24  func (a *Operator) minKubeVersionStatus(name string, minKubeVersion string) (met bool, statuses []v1alpha1.RequirementStatus) {
    25  	if minKubeVersion == "" {
    26  		return true, nil
    27  	}
    28  
    29  	status := v1alpha1.RequirementStatus{
    30  		Group:   "operators.coreos.com",
    31  		Version: "v1alpha1",
    32  		Kind:    "ClusterServiceVersion",
    33  		Name:    name,
    34  	}
    35  
    36  	// Retrieve server k8s version
    37  	serverVersionInfo, err := a.opClient.KubernetesInterface().Discovery().ServerVersion()
    38  	if err != nil {
    39  		status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied
    40  		status.Message = "Server version discovery error"
    41  		met = false
    42  		statuses = append(statuses, status)
    43  		return
    44  	}
    45  
    46  	serverVersion, err := semver.NewVersion(strings.Split(strings.TrimPrefix(serverVersionInfo.String(), "v"), "-")[0])
    47  	if err != nil {
    48  		status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied
    49  		status.Message = "Server version parsing error"
    50  		met = false
    51  		statuses = append(statuses, status)
    52  		return
    53  	}
    54  
    55  	csvVersionInfo, err := semver.NewVersion(strings.TrimPrefix(minKubeVersion, "v"))
    56  	if err != nil {
    57  		status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied
    58  		status.Message = "CSV version parsing error"
    59  		met = false
    60  		statuses = append(statuses, status)
    61  		return
    62  	}
    63  
    64  	if csvVersionInfo.Compare(*serverVersion) > 0 {
    65  		status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied
    66  		status.Message = fmt.Sprintf("CSV version requirement not met: minKubeVersion (%s) > server version (%s)", minKubeVersion, serverVersion.String())
    67  		met = false
    68  		statuses = append(statuses, status)
    69  		return
    70  	}
    71  
    72  	status.Status = v1alpha1.RequirementStatusReasonPresent
    73  	status.Message = fmt.Sprintf("CSV minKubeVersion (%s) less than server version (%s)", minKubeVersion, serverVersionInfo.String())
    74  	met = true
    75  	statuses = append(statuses, status)
    76  	return
    77  }
    78  
    79  func (a *Operator) requirementStatus(strategyDetailsDeployment *v1alpha1.StrategyDetailsDeployment, csv *v1alpha1.ClusterServiceVersion) (met bool, statuses []v1alpha1.RequirementStatus) {
    80  	ownedCRDNames := make(map[string]bool)
    81  	for _, owned := range csv.Spec.CustomResourceDefinitions.Owned {
    82  		ownedCRDNames[owned.Name] = true
    83  	}
    84  
    85  	crdDescs := csv.GetAllCRDDescriptions()
    86  	ownedAPIServiceDescs := csv.GetOwnedAPIServiceDescriptions()
    87  	requiredAPIServiceDescs := csv.GetRequiredAPIServiceDescriptions()
    88  	requiredNativeAPIs := csv.Spec.NativeAPIs
    89  	met = true
    90  
    91  	// Check for CRDs
    92  	for _, r := range crdDescs {
    93  		status := v1alpha1.RequirementStatus{
    94  			Group:   "apiextensions.k8s.io",
    95  			Version: "v1",
    96  			Kind:    "CustomResourceDefinition",
    97  			Name:    r.Name,
    98  		}
    99  
   100  		// check if CRD exists - this verifies group, version, and kind, so no need for GVK check via discovery
   101  		crd, err := a.opClient.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), r.Name, metav1.GetOptions{})
   102  		if err != nil {
   103  			status.Status = v1alpha1.RequirementStatusReasonNotPresent
   104  			status.Message = "CRD is not present"
   105  			a.logger.Debugf("Setting 'met' to false, %v with status %v, with err: %v", r.Name, status, err)
   106  			met = false
   107  			statuses = append(statuses, status)
   108  			continue
   109  		}
   110  
   111  		if others := othersInstalledAlongside(crd, csv, a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(csv.GetNamespace())); len(others) > 0 && ownedCRDNames[crd.Name] {
   112  			status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied
   113  			status.Message = fmt.Sprintf("CRD installed alongside other CSV(s): %s", strings.Join(others, ", "))
   114  			met = false
   115  			statuses = append(statuses, status)
   116  			continue
   117  		}
   118  
   119  		served := false
   120  		for _, version := range crd.Spec.Versions {
   121  			if version.Name == r.Version {
   122  				if version.Served {
   123  					served = true
   124  				}
   125  				break
   126  			}
   127  		}
   128  
   129  		if !served {
   130  			status.Status = v1alpha1.RequirementStatusReasonNotPresent
   131  			status.Message = "CRD version not served"
   132  			a.logger.Debugf("Setting 'met' to false, %v with status %v, CRD version %v not found", r.Name, status, r.Version)
   133  			met = false
   134  			statuses = append(statuses, status)
   135  			continue
   136  		}
   137  
   138  		// Check if CRD has successfully registered with k8s API
   139  		established := false
   140  		namesAccepted := false
   141  		for _, cdt := range crd.Status.Conditions {
   142  			switch cdt.Type {
   143  			case apiextensionsv1.Established:
   144  				if cdt.Status == apiextensionsv1.ConditionTrue {
   145  					established = true
   146  				}
   147  			case apiextensionsv1.NamesAccepted:
   148  				if cdt.Status == apiextensionsv1.ConditionTrue {
   149  					namesAccepted = true
   150  				}
   151  			}
   152  		}
   153  
   154  		if established && namesAccepted {
   155  			status.Status = v1alpha1.RequirementStatusReasonPresent
   156  			status.Message = "CRD is present and Established condition is true"
   157  			status.UUID = string(crd.GetUID())
   158  			statuses = append(statuses, status)
   159  		} else {
   160  			status.Status = v1alpha1.RequirementStatusReasonNotAvailable
   161  			status.Message = "CRD is present but the Established condition is False (not available)"
   162  			met = false
   163  			a.logger.Debugf("Setting 'met' to false, %v with status %v, established=%v, namesAccepted=%v", r.Name, status, established, namesAccepted)
   164  			statuses = append(statuses, status)
   165  		}
   166  	}
   167  
   168  	// Check for required API services
   169  	for _, r := range requiredAPIServiceDescs {
   170  		name := fmt.Sprintf("%s.%s", r.Version, r.Group)
   171  		status := v1alpha1.RequirementStatus{
   172  			Group:   "apiregistration.k8s.io",
   173  			Version: "v1",
   174  			Kind:    "APIService",
   175  			Name:    name,
   176  		}
   177  
   178  		// Check if GVK exists
   179  		if ok, err := a.isGVKRegistered(r.Group, r.Version, r.Kind); !ok || err != nil {
   180  			status.Status = "NotPresent"
   181  			met = false
   182  			statuses = append(statuses, status)
   183  			continue
   184  		}
   185  
   186  		// Check if APIService is registered
   187  		apiService, err := a.lister.APIRegistrationV1().APIServiceLister().Get(name)
   188  		if err != nil {
   189  			status.Status = "NotPresent"
   190  			met = false
   191  			statuses = append(statuses, status)
   192  			continue
   193  		}
   194  
   195  		// Check if API is available
   196  		if !install.IsAPIServiceAvailable(apiService) {
   197  			status.Status = "NotPresent"
   198  			met = false
   199  		} else {
   200  			status.Status = "Present"
   201  			status.UUID = string(apiService.GetUID())
   202  		}
   203  		statuses = append(statuses, status)
   204  	}
   205  
   206  	// Check owned API services
   207  	for _, r := range ownedAPIServiceDescs {
   208  		name := fmt.Sprintf("%s.%s", r.Version, r.Group)
   209  		status := v1alpha1.RequirementStatus{
   210  			Group:   "apiregistration.k8s.io",
   211  			Version: "v1",
   212  			Kind:    "APIService",
   213  			Name:    name,
   214  		}
   215  
   216  		found := false
   217  		for _, spec := range strategyDetailsDeployment.DeploymentSpecs {
   218  			if spec.Name == r.DeploymentName {
   219  				status.Status = "DeploymentFound"
   220  				statuses = append(statuses, status)
   221  				found = true
   222  				break
   223  			}
   224  		}
   225  
   226  		if !found {
   227  			status.Status = "DeploymentNotFound"
   228  			statuses = append(statuses, status)
   229  			met = false
   230  		}
   231  	}
   232  
   233  	for _, r := range requiredNativeAPIs {
   234  		name := fmt.Sprintf("%s.%s", r.Version, r.Group)
   235  		status := v1alpha1.RequirementStatus{
   236  			Group:   r.Group,
   237  			Version: r.Version,
   238  			Kind:    r.Kind,
   239  			Name:    name,
   240  		}
   241  
   242  		if ok, err := a.isGVKRegistered(r.Group, r.Version, r.Kind); !ok || err != nil {
   243  			status.Status = v1alpha1.RequirementStatusReasonNotPresent
   244  			status.Message = "Native API does not exist"
   245  			met = false
   246  			statuses = append(statuses, status)
   247  			continue
   248  		} else {
   249  			status.Status = v1alpha1.RequirementStatusReasonPresent
   250  			status.Message = "Native API exists"
   251  			statuses = append(statuses, status)
   252  			continue
   253  		}
   254  	}
   255  
   256  	return
   257  }
   258  
   259  // permissionStatus checks whether the given CSV's RBAC requirements are met in its namespace
   260  func (a *Operator) permissionStatus(strategyDetailsDeployment *v1alpha1.StrategyDetailsDeployment, targetNamespace string, csv *v1alpha1.ClusterServiceVersion) (bool, []v1alpha1.RequirementStatus, error) {
   261  	statusesSet := map[string]v1alpha1.RequirementStatus{}
   262  
   263  	checkPermissions := func(permissions []v1alpha1.StrategyDeploymentPermissions, namespace string) (bool, error) {
   264  		met := true
   265  		for _, perm := range permissions {
   266  			saName := perm.ServiceAccountName
   267  			a.logger.Debugf("perm.ServiceAccountName: %s", saName)
   268  
   269  			var status v1alpha1.RequirementStatus
   270  			if stored, ok := statusesSet[saName]; !ok {
   271  				status = v1alpha1.RequirementStatus{
   272  					Group:      "",
   273  					Version:    "v1",
   274  					Kind:       "ServiceAccount",
   275  					Name:       saName,
   276  					Status:     v1alpha1.RequirementStatusReasonPresent,
   277  					Dependents: []v1alpha1.DependentStatus{},
   278  				}
   279  			} else {
   280  				status = stored
   281  			}
   282  
   283  			// Ensure the ServiceAccount exists
   284  			sa, err := a.opClient.GetServiceAccount(csv.GetNamespace(), perm.ServiceAccountName)
   285  			if err != nil {
   286  				met = false
   287  				status.Status = v1alpha1.RequirementStatusReasonNotPresent
   288  				status.Message = "Service account does not exist"
   289  				statusesSet[saName] = status
   290  				continue
   291  			}
   292  			// Check SA's ownership
   293  			if ownerutil.IsOwnedByKind(sa, v1alpha1.ClusterServiceVersionKind) && !ownerutil.IsOwnedBy(sa, csv) {
   294  				met = false
   295  				status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied
   296  				status.Message = "Service account is owned by another ClusterServiceVersion"
   297  				statusesSet[saName] = status
   298  				continue
   299  			}
   300  
   301  			// Check if PolicyRules are satisfied
   302  			if a.informersFiltered {
   303  				// we don't hold the whole set of RBAC in memory, so we can't use the authorizer:
   304  				// check for rules we would have created ourselves first
   305  				var err error
   306  				var permissionMet bool
   307  				if namespace == metav1.NamespaceAll {
   308  					permissionMet, err = permissionsPreviouslyCreated[*rbacv1.ClusterRole, *rbacv1.ClusterRoleBinding](
   309  						perm, csv,
   310  						a.lister.RbacV1().ClusterRoleLister().List, a.lister.RbacV1().ClusterRoleBindingLister().List,
   311  					)
   312  				} else {
   313  					permissionMet, err = permissionsPreviouslyCreated[*rbacv1.Role, *rbacv1.RoleBinding](
   314  						perm, csv,
   315  						a.lister.RbacV1().RoleLister().Roles(namespace).List, a.lister.RbacV1().RoleBindingLister().RoleBindings(namespace).List,
   316  					)
   317  				}
   318  				if err != nil {
   319  					return false, err
   320  				}
   321  				if permissionMet {
   322  					// OLM previously made all the permissions we need, exit early
   323  					for _, rule := range perm.Rules {
   324  						dependent := v1alpha1.DependentStatus{
   325  							Group:   "rbac.authorization.k8s.io",
   326  							Kind:    "PolicyRule",
   327  							Version: "v1",
   328  							Status:  v1alpha1.DependentStatusReasonSatisfied,
   329  						}
   330  						marshalled, err := json.Marshal(rule)
   331  						if err != nil {
   332  							dependent.Status = v1alpha1.DependentStatusReasonNotSatisfied
   333  							dependent.Message = "rule unmarshallable"
   334  							status.Dependents = append(status.Dependents, dependent)
   335  							continue
   336  						}
   337  
   338  						var scope string
   339  						if namespace == metav1.NamespaceAll {
   340  							scope = "cluster"
   341  						} else {
   342  							scope = "namespaced"
   343  						}
   344  						dependent.Message = fmt.Sprintf("%s rule:%s", scope, marshalled)
   345  						status.Dependents = append(status.Dependents, dependent)
   346  					}
   347  					continue
   348  				}
   349  			}
   350  			// if we have not filtered our informers or if we were unable to detect the correct permissions, we have
   351  			// no choice but to page in the world and see if the user pre-created permissions for this CSV
   352  			ruleChecker := a.getRuleChecker()(csv)
   353  			if ruleChecker == nil {
   354  				return false, errors.New("could not create a rule checker (are we shutting down?)")
   355  			}
   356  			for _, rule := range perm.Rules {
   357  				dependent := v1alpha1.DependentStatus{
   358  					Group:   "rbac.authorization.k8s.io",
   359  					Kind:    "PolicyRule",
   360  					Version: "v1",
   361  				}
   362  
   363  				marshalled, err := json.Marshal(rule)
   364  				if err != nil {
   365  					dependent.Status = v1alpha1.DependentStatusReasonNotSatisfied
   366  					dependent.Message = "rule unmarshallable"
   367  					status.Dependents = append(status.Dependents, dependent)
   368  					continue
   369  				}
   370  
   371  				var scope string
   372  				if namespace == metav1.NamespaceAll {
   373  					scope = "cluster"
   374  				} else {
   375  					scope = "namespaced"
   376  				}
   377  				dependent.Message = fmt.Sprintf("%s rule:%s", scope, marshalled)
   378  
   379  				satisfied, err := ruleChecker.RuleSatisfied(sa, namespace, rule)
   380  				if err != nil {
   381  					return false, err
   382  				} else if !satisfied {
   383  					met = false
   384  					dependent.Status = v1alpha1.DependentStatusReasonNotSatisfied
   385  					status.Status = v1alpha1.RequirementStatusReasonPresentNotSatisfied
   386  					status.Message = "Policy rule not satisfied for service account"
   387  				} else {
   388  					dependent.Status = v1alpha1.DependentStatusReasonSatisfied
   389  				}
   390  
   391  				status.Dependents = append(status.Dependents, dependent)
   392  			}
   393  
   394  			statusesSet[saName] = status
   395  		}
   396  
   397  		return met, nil
   398  	}
   399  
   400  	permMet, err := checkPermissions(strategyDetailsDeployment.Permissions, targetNamespace)
   401  	if err != nil {
   402  		return false, nil, err
   403  	}
   404  	clusterPermMet, err := checkPermissions(strategyDetailsDeployment.ClusterPermissions, metav1.NamespaceAll)
   405  	if err != nil {
   406  		return false, nil, err
   407  	}
   408  
   409  	statuses := []v1alpha1.RequirementStatus{}
   410  	for key, status := range statusesSet {
   411  		a.logger.WithField("key", key).WithField("status", status).Tracef("appending permission status")
   412  		statuses = append(statuses, status)
   413  	}
   414  
   415  	return permMet && clusterPermMet, statuses, nil
   416  }
   417  
   418  func permissionsPreviouslyCreated[T, U metav1.Object](
   419  	permission v1alpha1.StrategyDeploymentPermissions,
   420  	csv *v1alpha1.ClusterServiceVersion,
   421  	listRoles func(labels.Selector) ([]T, error),
   422  	listBindings func(labels.Selector) ([]U, error),
   423  ) (bool, error) {
   424  	// first, find the (cluster)role
   425  	ruleHash, err := resolver.PolicyRuleHashLabelValue(permission.Rules)
   426  	if err != nil {
   427  		return false, fmt.Errorf("failed to hash permission rules: %w", err)
   428  	}
   429  	roleSelectorMap := ownerutil.OwnerLabel(csv, v1alpha1.ClusterServiceVersionKind)
   430  	roleSelectorMap[resolver.ContentHashLabelKey] = ruleHash
   431  	roles, err := listRoles(labels.SelectorFromSet(roleSelectorMap))
   432  	if err != nil {
   433  		return false, err
   434  	}
   435  
   436  	if len(roles) == 0 {
   437  		return false, nil
   438  	}
   439  
   440  	// then, find the (cluster)rolebinding, if we found the role
   441  	bindingHash, err := resolver.RoleReferenceAndSubjectHashLabelValue(rbacv1.RoleRef{
   442  		Kind:     "Role",
   443  		Name:     roles[0].GetName(),
   444  		APIGroup: rbacv1.GroupName,
   445  	},
   446  		[]rbacv1.Subject{{
   447  			Kind:      "ServiceAccount",
   448  			Name:      permission.ServiceAccountName,
   449  			Namespace: csv.GetNamespace(),
   450  		}},
   451  	)
   452  	if err != nil {
   453  		return false, fmt.Errorf("failed to hash binding content: %w", err)
   454  	}
   455  	bindingSelectorMap := ownerutil.OwnerLabel(csv, v1alpha1.ClusterServiceVersionKind)
   456  	bindingSelectorMap[resolver.ContentHashLabelKey] = bindingHash
   457  	bindingSelectorSet := labels.Set{}
   458  	for key, value := range bindingSelectorMap {
   459  		bindingSelectorSet[key] = value
   460  	}
   461  	bindingSelector := labels.SelectorFromSet(bindingSelectorSet)
   462  	bindings, err := listBindings(bindingSelector)
   463  	return len(roles) > 0 && len(bindings) > 0, err
   464  }
   465  
   466  // requirementAndPermissionStatus returns the aggregate requirement and permissions statuses for the given CSV
   467  func (a *Operator) requirementAndPermissionStatus(csv *v1alpha1.ClusterServiceVersion) (bool, []v1alpha1.RequirementStatus, error) {
   468  	allReqStatuses := []v1alpha1.RequirementStatus{}
   469  	// Use a StrategyResolver to unmarshal
   470  	strategyResolver := install.StrategyResolver{}
   471  	strategy, err := strategyResolver.UnmarshalStrategy(csv.Spec.InstallStrategy)
   472  	if err != nil {
   473  		return false, nil, err
   474  	}
   475  
   476  	// Assume the strategy is for a deployment
   477  	strategyDetailsDeployment, ok := strategy.(*v1alpha1.StrategyDetailsDeployment)
   478  	if !ok {
   479  		return false, nil, fmt.Errorf("could not cast install strategy as type %T", strategyDetailsDeployment)
   480  	}
   481  
   482  	// Check kubernetes version requirement between CSV and server
   483  	minKubeMet, minKubeStatus := a.minKubeVersionStatus(csv.GetName(), csv.Spec.MinKubeVersion)
   484  	if minKubeStatus != nil {
   485  		allReqStatuses = append(allReqStatuses, minKubeStatus...)
   486  	}
   487  
   488  	reqMet, reqStatuses := a.requirementStatus(strategyDetailsDeployment, csv)
   489  	allReqStatuses = append(allReqStatuses, reqStatuses...)
   490  
   491  	permMet, permStatuses, err := a.permissionStatus(strategyDetailsDeployment, csv.GetNamespace(), csv)
   492  	if err != nil {
   493  		return false, nil, err
   494  	}
   495  
   496  	// Aggregate requirement and permissions statuses
   497  	statuses := append(allReqStatuses, permStatuses...)
   498  	met := minKubeMet && reqMet && permMet
   499  	if !met {
   500  		a.logger.WithField("minKubeMet", minKubeMet).WithField("reqMet", reqMet).WithField("permMet", permMet).Debug("permissions/requirements not met")
   501  	}
   502  
   503  	return met, statuses, nil
   504  }
   505  
   506  func (a *Operator) isGVKRegistered(group, version, kind string) (bool, error) {
   507  	logger := a.logger.WithFields(logrus.Fields{
   508  		"group":   group,
   509  		"version": version,
   510  		"kind":    kind,
   511  	})
   512  
   513  	gv := metav1.GroupVersion{Group: group, Version: version}
   514  	resources, err := a.opClient.KubernetesInterface().Discovery().ServerResourcesForGroupVersion(gv.String())
   515  	if err != nil {
   516  		logger.WithField("err", err).Info("could not query for GVK in api discovery")
   517  		return false, err
   518  	}
   519  
   520  	for _, r := range resources.APIResources {
   521  		if r.Kind == kind {
   522  			return true, nil
   523  		}
   524  	}
   525  
   526  	logger.Info("couldn't find GVK in api discovery")
   527  	return false, nil
   528  }
   529  
   530  // othersInstalledAlongside returns the names of all
   531  // ClusterServiceVersions alongside which the given object was
   532  // installed, that are not the named CSV and are directly or
   533  // transitively replaced by the named CSV.
   534  func othersInstalledAlongside(o metav1.Object, target *v1alpha1.ClusterServiceVersion, lister listersv1alpha1.ClusterServiceVersionNamespaceLister) []string {
   535  	csvsByName := make(map[string]*v1alpha1.ClusterServiceVersion)
   536  	for _, nn := range (alongside.Annotator{}).FromObject(o) {
   537  		if nn.Namespace != target.GetNamespace() {
   538  			continue
   539  		}
   540  		if nn.Name == target.GetName() {
   541  			return nil
   542  		}
   543  		csv, err := lister.Get(nn.Name)
   544  		if err != nil || csv.IsCopied() {
   545  			continue
   546  		}
   547  		csvsByName[csv.GetName()] = csv
   548  	}
   549  
   550  	replacees := make(map[string]string)
   551  	for current, csv := range csvsByName {
   552  		if _, ok := csvsByName[csv.Spec.Replaces]; ok {
   553  			replacees[current] = csv.Spec.Replaces
   554  		}
   555  	}
   556  	if target.Spec.Replaces != "" {
   557  		replacees[target.GetName()] = target.Spec.Replaces
   558  	}
   559  
   560  	ancestors := make(map[string]struct{})
   561  	for current := target.GetName(); current != ""; {
   562  		replacee, ok := replacees[current]
   563  		if ok {
   564  			ancestors[replacee] = struct{}{}
   565  		}
   566  		delete(replacees, current) // avoid cycles
   567  		current = replacee
   568  	}
   569  
   570  	var names []string
   571  	for each := range csvsByName {
   572  		if _, ok := ancestors[each]; ok && each != target.GetName() {
   573  			names = append(names, each)
   574  		}
   575  	}
   576  	return names
   577  }