github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/install/apiservice.go (about)

     1  package install
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	log "github.com/sirupsen/logrus"
     9  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    10  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	"k8s.io/apimachinery/pkg/labels"
    12  	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
    13  
    14  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    15  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
    16  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
    17  )
    18  
    19  func (i *StrategyDeploymentInstaller) createOrUpdateAPIService(caPEM []byte, desc v1alpha1.APIServiceDescription) error {
    20  	apiServiceName := fmt.Sprintf("%s.%s", desc.Version, desc.Group)
    21  	logger := log.WithFields(log.Fields{
    22  		"owner":      i.owner.GetName(),
    23  		"namespace":  i.owner.GetNamespace(),
    24  		"apiservice": fmt.Sprintf("%s.%s", desc.Version, desc.Group),
    25  	})
    26  
    27  	exists := true
    28  	apiService, err := i.strategyClient.GetOpLister().APIRegistrationV1().APIServiceLister().Get(apiServiceName)
    29  	if err != nil {
    30  		if !apierrors.IsNotFound(err) {
    31  			return err
    32  		}
    33  
    34  		exists = false
    35  		apiService = &apiregistrationv1.APIService{
    36  			Spec: apiregistrationv1.APIServiceSpec{
    37  				Group:                desc.Group,
    38  				Version:              desc.Version,
    39  				GroupPriorityMinimum: int32(2000),
    40  				VersionPriority:      int32(15),
    41  			},
    42  		}
    43  		apiService.SetName(apiServiceName)
    44  
    45  		ownerSubscription, err := i.findOwnerSubscription()
    46  		if err != nil {
    47  			return err
    48  		} else if ownerSubscription == nil {
    49  			// This is not an error. For example, the PackageServer CSV in OLM is created without a Subscription.
    50  			logger.Debugf("failed to get the owner subscription csv=%s", i.owner.GetName())
    51  		} else if ownerSubscription.Spec.Config != nil {
    52  			apiService.SetAnnotations(ownerSubscription.Spec.Config.Annotations)
    53  		}
    54  	} else {
    55  		apiService = apiService.DeepCopy()
    56  		csv, ok := i.owner.(*v1alpha1.ClusterServiceVersion)
    57  		if !ok {
    58  			return fmt.Errorf("failed to typecast the APIService owner: APIServices require a CSV owner")
    59  		}
    60  
    61  		adoptable, err := IsAPIServiceAdoptable(i.strategyClient.GetOpLister(), csv, apiService)
    62  		if err != nil {
    63  			logger.WithFields(log.Fields{"obj": "apiService", "labels": apiService.GetLabels()}).Errorf("adoption check failed - %v", err)
    64  		}
    65  
    66  		if !adoptable {
    67  			return fmt.Errorf("pre-existing APIService %s.%s is not adoptable", desc.Version, desc.Group)
    68  		}
    69  	}
    70  
    71  	// Add the CSV as an owner
    72  	if err := ownerutil.AddOwnerLabels(apiService, i.owner); err != nil {
    73  		return err
    74  	}
    75  	apiService.Labels[OLMManagedLabelKey] = OLMManagedLabelValue
    76  
    77  	// Create a service for the deployment
    78  	containerPort := int32(443)
    79  	if desc.ContainerPort > 0 {
    80  		containerPort = desc.ContainerPort
    81  	}
    82  	// update the ServiceReference
    83  	apiService.Spec.Service = &apiregistrationv1.ServiceReference{
    84  		Namespace: i.owner.GetNamespace(),
    85  		Name:      ServiceName(desc.DeploymentName),
    86  		Port:      &containerPort,
    87  	}
    88  
    89  	// create a fresh CA bundle
    90  	apiService.Spec.CABundle = caPEM
    91  
    92  	// attempt a update or create
    93  	if exists {
    94  		logger.Debug("updating APIService")
    95  		_, err = i.strategyClient.GetOpClient().UpdateAPIService(apiService)
    96  	} else {
    97  		logger.Debug("creating APIService")
    98  		_, err = i.strategyClient.GetOpClient().CreateAPIService(apiService)
    99  	}
   100  
   101  	if err != nil {
   102  		logger.Warnf("could not create or update APIService")
   103  		return err
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  func IsAPIServiceAdoptable(opLister operatorlister.OperatorLister, target *v1alpha1.ClusterServiceVersion, apiService *apiregistrationv1.APIService) (adoptable bool, err error) {
   110  	if apiService == nil || target == nil {
   111  		err = errors.New("invalid input")
   112  		return
   113  	}
   114  
   115  	apiServiceLabels := apiService.GetLabels()
   116  	ownerKind := apiServiceLabels[ownerutil.OwnerKind]
   117  	ownerName := apiServiceLabels[ownerutil.OwnerKey]
   118  	ownerNamespace := apiServiceLabels[ownerutil.OwnerNamespaceKey]
   119  
   120  	if ownerKind == "" || ownerNamespace == "" || ownerName == "" {
   121  		return
   122  	}
   123  
   124  	if err := ownerutil.InferGroupVersionKind(target); err != nil {
   125  		log.Warn(err.Error())
   126  	}
   127  
   128  	targetKind := target.GetObjectKind().GroupVersionKind().Kind
   129  	if ownerKind != targetKind {
   130  		return
   131  	}
   132  
   133  	// Get the CSV that target replaces
   134  	replacing, replaceGetErr := opLister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(target.GetNamespace()).Get(target.Spec.Replaces)
   135  	if replaceGetErr != nil && !apierrors.IsNotFound(replaceGetErr) && !apierrors.IsGone(replaceGetErr) {
   136  		err = replaceGetErr
   137  		return
   138  	}
   139  
   140  	// Get the current owner CSV of the APIService
   141  	currentOwnerCSV, ownerGetErr := opLister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(ownerNamespace).Get(ownerName)
   142  	if ownerGetErr != nil && !apierrors.IsNotFound(ownerGetErr) && !apierrors.IsGone(ownerGetErr) {
   143  		err = ownerGetErr
   144  		return
   145  	}
   146  
   147  	owners := []ownerutil.Owner{target}
   148  	if replacing != nil {
   149  		owners = append(owners, replacing)
   150  	}
   151  	if currentOwnerCSV != nil && (currentOwnerCSV.Status.Phase == v1alpha1.CSVPhaseReplacing || currentOwnerCSV.Status.Phase == v1alpha1.CSVPhaseDeleting) {
   152  		owners = append(owners, currentOwnerCSV)
   153  	}
   154  
   155  	adoptable = ownerutil.AdoptableLabels(apiService.GetLabels(), true, owners...)
   156  	return
   157  }
   158  
   159  func IsAPIServiceAvailable(apiService *apiregistrationv1.APIService) bool {
   160  	for _, c := range apiService.Status.Conditions {
   161  		if c.Type == apiregistrationv1.Available && c.Status == apiregistrationv1.ConditionTrue {
   162  			return true
   163  		}
   164  	}
   165  	return false
   166  }
   167  
   168  // deleteLegacyAPIServiceResources deletes resources that were created by OLM for an APIService that used the old naming convention.
   169  func (i *StrategyDeploymentInstaller) deleteLegacyAPIServiceResources(desc apiServiceDescriptionsWithCAPEM) error {
   170  	logger := log.WithFields(log.Fields{
   171  		"ownerName":      i.owner.GetName(),
   172  		"ownerNamespace": i.owner.GetNamespace(),
   173  		"ownerKind":      i.owner.GetObjectKind().GroupVersionKind().GroupKind().Kind,
   174  	})
   175  	namespace := i.owner.GetNamespace()
   176  	apiServiceName := fmt.Sprintf("%s.%s", desc.apiServiceDescription.Version, desc.apiServiceDescription.Group)
   177  
   178  	// Handle an edgecase where the legacy resources names matches the new names.
   179  	// This only occurs when the Group of the description matches the name of the deployment
   180  	// and the version is equal to "service".
   181  	if legacyAPIServiceNameToServiceName(apiServiceName) == ServiceName(desc.apiServiceDescription.DeploymentName) {
   182  		return nil
   183  	}
   184  
   185  	// Handle an edgecase where the legacy resources names matches the new names.
   186  	// This only occurs when the version of the description matches the name of the deployment
   187  	// and the group is equal to "service"
   188  	// If the names match, do not delete the service as OLM has already updated it.
   189  	legacyServiceName := legacyAPIServiceNameToServiceName(apiServiceName)
   190  	if legacyServiceName != ServiceName(desc.apiServiceDescription.DeploymentName) {
   191  		// Attempt to delete the legacy Service.
   192  		existingService, err := i.strategyClient.GetOpClient().GetService(namespace, legacyServiceName)
   193  		if err != nil {
   194  			if !apierrors.IsNotFound(err) {
   195  				return err
   196  			}
   197  		} else if ownerutil.AdoptableLabels(existingService.GetLabels(), true, i.owner) {
   198  			logger.Infof("Deleting Service with legacy APIService name %s", existingService.Name)
   199  			err = i.strategyClient.GetOpClient().DeleteService(namespace, legacyServiceName, &metav1.DeleteOptions{})
   200  			if err != nil && !apierrors.IsNotFound(err) {
   201  				return err
   202  			}
   203  		} else {
   204  			logger.Infof("Service with legacy APIService resource name %s not adoptable", existingService.Name)
   205  		}
   206  	} else {
   207  		logger.Infof("New Service name matches legacy APIService resource name %s", legacyServiceName)
   208  	}
   209  
   210  	// Attempt to delete the legacy Secret.
   211  	existingSecret, err := i.strategyClient.GetOpClient().GetSecret(namespace, SecretName(apiServiceName))
   212  	if err != nil {
   213  		if !apierrors.IsNotFound(err) {
   214  			return err
   215  		}
   216  	} else if ownerutil.AdoptableLabels(existingSecret.GetLabels(), true, i.owner) {
   217  		logger.Infof("Deleting Secret with legacy APIService name %s", existingSecret.Name)
   218  		err = i.strategyClient.GetOpClient().DeleteSecret(namespace, SecretName(apiServiceName), &metav1.DeleteOptions{})
   219  		if err != nil && !apierrors.IsNotFound(err) {
   220  			return err
   221  		}
   222  	} else {
   223  		logger.Infof("Secret with legacy APIService  %s not adoptable", existingSecret.Name)
   224  	}
   225  
   226  	// Attempt to delete the legacy Role.
   227  	existingRole, err := i.strategyClient.GetOpClient().GetRole(namespace, SecretName(apiServiceName))
   228  	if err != nil {
   229  		if !apierrors.IsNotFound(err) {
   230  			return err
   231  		}
   232  	} else if ownerutil.AdoptableLabels(existingRole.GetLabels(), true, i.owner) {
   233  		logger.Infof("Deleting Role with legacy APIService name %s", existingRole.Name)
   234  		err = i.strategyClient.GetOpClient().DeleteRole(namespace, SecretName(apiServiceName), &metav1.DeleteOptions{})
   235  		if err != nil && !apierrors.IsNotFound(err) {
   236  			return err
   237  		}
   238  	} else {
   239  		logger.Infof("Role with legacy APIService name %s not adoptable", existingRole.Name)
   240  	}
   241  
   242  	// Attempt to delete the legacy secret RoleBinding.
   243  	existingRoleBinding, err := i.strategyClient.GetOpClient().GetRoleBinding(namespace, SecretName(apiServiceName))
   244  	if err != nil {
   245  		if !apierrors.IsNotFound(err) {
   246  			return err
   247  		}
   248  	} else if ownerutil.AdoptableLabels(existingRoleBinding.GetLabels(), true, i.owner) {
   249  		logger.Infof("Deleting RoleBinding with legacy APIService name %s", existingRoleBinding.Name)
   250  		err = i.strategyClient.GetOpClient().DeleteRoleBinding(namespace, SecretName(apiServiceName), &metav1.DeleteOptions{})
   251  		if err != nil && !apierrors.IsNotFound(err) {
   252  			return err
   253  		}
   254  	} else {
   255  		logger.Infof("RoleBinding with legacy APIService name %s not adoptable", existingRoleBinding.Name)
   256  	}
   257  
   258  	// Attempt to delete the legacy ClusterRoleBinding.
   259  	existingClusterRoleBinding, err := i.strategyClient.GetOpClient().GetClusterRoleBinding(apiServiceName + "-system:auth-delegator")
   260  	if err != nil {
   261  		if !apierrors.IsNotFound(err) {
   262  			return err
   263  		}
   264  	} else if ownerutil.AdoptableLabels(existingClusterRoleBinding.GetLabels(), true, i.owner) {
   265  		logger.Infof("Deleting ClusterRoleBinding with legacy APIService name %s", existingClusterRoleBinding.Name)
   266  		err = i.strategyClient.GetOpClient().DeleteClusterRoleBinding(apiServiceName+"-system:auth-delegator", &metav1.DeleteOptions{})
   267  		if err != nil && !apierrors.IsNotFound(err) {
   268  			return err
   269  		}
   270  	} else {
   271  		logger.Infof("ClusterRoleBinding with legacy APIService name %s not adoptable", existingClusterRoleBinding.Name)
   272  	}
   273  
   274  	// Attempt to delete the legacy AuthReadingRoleBinding.
   275  	existingRoleBinding, err = i.strategyClient.GetOpClient().GetRoleBinding(KubeSystem, apiServiceName+"-auth-reader")
   276  	if err != nil {
   277  		if !apierrors.IsNotFound(err) {
   278  			return err
   279  		}
   280  	} else if ownerutil.AdoptableLabels(existingRoleBinding.GetLabels(), true, i.owner) {
   281  		logger.Infof("Deleting RoleBinding with legacy APIService name %s", existingRoleBinding.Name)
   282  		err = i.strategyClient.GetOpClient().DeleteRoleBinding(KubeSystem, apiServiceName+"-auth-reader", &metav1.DeleteOptions{})
   283  		if err != nil && !apierrors.IsNotFound(err) {
   284  			return err
   285  		}
   286  	} else {
   287  		logger.Infof("RoleBinding with legacy APIService name %s not adoptable", existingRoleBinding.Name)
   288  	}
   289  
   290  	return nil
   291  }
   292  
   293  // legacyAPIServiceNameToServiceName returns the result of replacing all
   294  // periods in the given APIService name with hyphens
   295  func legacyAPIServiceNameToServiceName(apiServiceName string) string {
   296  	// Replace all '.'s with "-"s to convert to a DNS-1035 label
   297  	return strings.Replace(apiServiceName, ".", "-", -1)
   298  }
   299  
   300  func (i *StrategyDeploymentInstaller) findOwnerSubscription() (*v1alpha1.Subscription, error) {
   301  	list, listErr := i.strategyClient.GetOpLister().OperatorsV1alpha1().SubscriptionLister().Subscriptions(i.owner.GetNamespace()).List(labels.Everything())
   302  	if listErr != nil {
   303  		err := fmt.Errorf("failed to list subscription namespace=%s - %v", i.owner.GetNamespace(), listErr)
   304  		return nil, err
   305  	}
   306  
   307  	for idx := range list {
   308  		sub := list[idx]
   309  		if sub.Status.InstalledCSV == i.owner.GetName() {
   310  			return sub, nil
   311  		}
   312  	}
   313  
   314  	return nil, nil
   315  }