github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/fail_forward.go (about)

     1  package resolver
     2  
     3  import (
     4  	"fmt"
     5  
     6  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
     7  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
     8  	v1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1"
     9  	"k8s.io/apimachinery/pkg/labels"
    10  )
    11  
    12  // IsFailForwardEnabled takes a namespaced operatorGroup lister and returns
    13  // True if an operatorGroup exists in the namespace and its upgradeStrategy
    14  // is set to UnsafeFailForward and false otherwise. An error is returned if
    15  // an more than one operatorGroup exists in the namespace.
    16  // No error is returned if no OperatorGroups are found to keep the resolver
    17  // backwards compatible.
    18  func IsFailForwardEnabled(ogLister v1listers.OperatorGroupNamespaceLister) (bool, error) {
    19  	ogs, err := ogLister.List(labels.Everything())
    20  	if err != nil {
    21  		return false, err
    22  	}
    23  	if len(ogs) != 1 {
    24  		return false, fmt.Errorf("found %d operatorGroups, expected 1", len(ogs))
    25  	}
    26  	return ogs[0].UpgradeStrategy() == operatorsv1.UpgradeStrategyUnsafeFailForward, nil
    27  }
    28  
    29  type walkOption func(csv *operatorsv1alpha1.ClusterServiceVersion) error
    30  
    31  // WithCSVPhase returns an error if the CSV is not in the given phase.
    32  func WithCSVPhase(phase operatorsv1alpha1.ClusterServiceVersionPhase) walkOption {
    33  	return func(csv *operatorsv1alpha1.ClusterServiceVersion) error {
    34  		if csv == nil || csv.Status.Phase != phase {
    35  			return fmt.Errorf("csv %s/%s in phase %s instead of %s", csv.GetNamespace(), csv.GetName(), csv.Status.Phase, phase)
    36  		}
    37  		return nil
    38  	}
    39  }
    40  
    41  // WithUniqueCSVs returns an error if the CSV has been seen before.
    42  func WithUniqueCSVs() walkOption {
    43  	visited := map[string]struct{}{}
    44  	return func(csv *operatorsv1alpha1.ClusterServiceVersion) error {
    45  		// Check if we have visited the CSV before
    46  		if _, ok := visited[csv.GetName()]; ok {
    47  			return fmt.Errorf("csv %s/%s has already been seen", csv.GetNamespace(), csv.GetName())
    48  		}
    49  
    50  		visited[csv.GetName()] = struct{}{}
    51  		return nil
    52  	}
    53  }
    54  
    55  // WalkReplacementChain walks along the chain of clusterServiceVersions being replaced and returns
    56  // the last clusterServiceVersions in the replacement chain. An error is returned if any of the
    57  // clusterServiceVersions before the last is not in the replaces phase or if an infinite replacement
    58  // chain is detected.
    59  func WalkReplacementChain(csv *operatorsv1alpha1.ClusterServiceVersion, csvToReplacement map[string]*operatorsv1alpha1.ClusterServiceVersion, options ...walkOption) (*operatorsv1alpha1.ClusterServiceVersion, error) {
    60  	if csv == nil {
    61  		return nil, fmt.Errorf("csv cannot be nil")
    62  	}
    63  
    64  	for {
    65  		// Check if there is a CSV that replaces this CSVs
    66  		next, ok := csvToReplacement[csv.GetName()]
    67  		if !ok {
    68  			break
    69  		}
    70  
    71  		// Check walk options
    72  		for _, o := range options {
    73  			if err := o(csv); err != nil {
    74  				return nil, err
    75  			}
    76  		}
    77  
    78  		// Move along replacement chain.
    79  		csv = next
    80  	}
    81  	return csv, nil
    82  }
    83  
    84  // isReplacementChainThatEndsInFailure returns true if the last CSV in the chain is in the failed phase and all other
    85  // CSVs are in the replacing phase.
    86  func isReplacementChainThatEndsInFailure(csv *operatorsv1alpha1.ClusterServiceVersion, csvToReplacement map[string]*operatorsv1alpha1.ClusterServiceVersion) (bool, error) {
    87  	lastCSV, err := WalkReplacementChain(csv, csvToReplacement, WithCSVPhase(operatorsv1alpha1.CSVPhaseReplacing), WithUniqueCSVs())
    88  	if err != nil {
    89  		return false, err
    90  	}
    91  	return (lastCSV != nil && lastCSV.Status.Phase == operatorsv1alpha1.CSVPhaseFailed), nil
    92  }
    93  
    94  // ReplacementMapping takes a list of CSVs and returns a map that maps a CSV's name to the CSV that replaces it.
    95  func ReplacementMapping(csvs []*operatorsv1alpha1.ClusterServiceVersion) map[string]*operatorsv1alpha1.ClusterServiceVersion {
    96  	replacementMapping := map[string]*operatorsv1alpha1.ClusterServiceVersion{}
    97  	for _, csv := range csvs {
    98  		if csv.Spec.Replaces != "" {
    99  			replacementMapping[csv.Spec.Replaces] = csv
   100  		}
   101  	}
   102  	return replacementMapping
   103  }