...

Source file src/github.com/redhat-developer/odo/pkg/kclient/binding.go

Documentation: github.com/redhat-developer/odo/pkg/kclient

     1  package kclient
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"github.com/redhat-developer/odo/pkg/api"
     8  
     9  	bindingApi "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
    10  	specApi "github.com/redhat-developer/service-binding-operator/apis/spec/v1alpha3"
    11  
    12  	ocappsv1 "github.com/openshift/api/apps/v1"
    13  
    14  	appsv1 "k8s.io/api/apps/v1"
    15  	corev1 "k8s.io/api/core/v1"
    16  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    17  	"k8s.io/apimachinery/pkg/api/meta"
    18  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    20  	"k8s.io/apimachinery/pkg/runtime/schema"
    21  )
    22  
    23  const (
    24  	ServiceBindingKind    = "ServiceBinding"
    25  	BindableKindsResource = "bindablekinds"
    26  )
    27  
    28  var (
    29  	NativeWorkloadKinds = []schema.GroupVersionKind{
    30  		appsv1.SchemeGroupVersion.WithKind("DaemonSet"),
    31  		appsv1.SchemeGroupVersion.WithKind("Deployment"),
    32  		appsv1.SchemeGroupVersion.WithKind("ReplicaSet"),
    33  		corev1.SchemeGroupVersion.WithKind("ReplicationController"),
    34  		appsv1.SchemeGroupVersion.WithKind("StatefulSet"),
    35  	}
    36  
    37  	CustomWorkloadKinds = []schema.GroupVersionKind{
    38  		ocappsv1.SchemeGroupVersion.WithKind("DeploymentConfig"),
    39  	}
    40  )
    41  
    42  // IsServiceBindingSupported checks if resource of type service binding request present on the cluster
    43  func (c *Client) IsServiceBindingSupported() (bool, error) {
    44  	gvr := bindingApi.GroupVersionResource
    45  	return c.IsResourceSupported(gvr.Group, gvr.Version, gvr.Resource)
    46  }
    47  
    48  // GetBindableKinds returns BindableKinds of name "bindable-kinds".
    49  // "bindable-kinds" is the default resource provided by SBO
    50  func (c *Client) GetBindableKinds() (bindingApi.BindableKinds, error) {
    51  	if c.DynamicClient == nil {
    52  		return bindingApi.BindableKinds{}, nil
    53  	}
    54  
    55  	var (
    56  		unstructuredBK *unstructured.Unstructured
    57  		bindableKind   bindingApi.BindableKinds
    58  		err            error
    59  	)
    60  
    61  	unstructuredBK, err = c.DynamicClient.Resource(bindingApi.GroupVersion.WithResource(BindableKindsResource)).Get(context.TODO(), "bindable-kinds", v1.GetOptions{})
    62  	if err != nil {
    63  		if kerrors.IsNotFound(err) {
    64  			//revive:disable:error-strings This is a top-level error message displayed as is to the end user
    65  			return bindableKind, errors.New("No bindable operators found on the cluster. Please ensure that at least one bindable operator is installed successfully before proceeding. Known Bindable operators: https://github.com/redhat-developer/service-binding-operator#known-bindable-operators")
    66  			//revive:enable:error-strings
    67  		}
    68  		return bindableKind, err
    69  	}
    70  
    71  	err = ConvertUnstructuredToResource(*unstructuredBK, &bindableKind)
    72  	if err != nil {
    73  		return bindableKind, err
    74  	}
    75  	return bindableKind, nil
    76  }
    77  
    78  // GetBindableKindStatusRestMapping returns a list of *meta.RESTMapping of all the bindable kind operator CRD
    79  func (c Client) GetBindableKindStatusRestMapping(bindableKindStatuses []bindingApi.BindableKindsStatus) ([]*meta.RESTMapping, error) {
    80  	var result []*meta.RESTMapping
    81  	for _, bks := range bindableKindStatuses {
    82  		if mappingContainsBKS(result, bks) {
    83  			continue
    84  		}
    85  		restMapping, err := c.GetRestMappingFromGVK(schema.GroupVersionKind{
    86  			Group:   bks.Group,
    87  			Version: bks.Version,
    88  			Kind:    bks.Kind,
    89  		})
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  		result = append(result, restMapping)
    94  	}
    95  	return result, nil
    96  }
    97  
    98  // NewServiceBindingServiceObject returns the bindingApi.Service object based on the RESTMapping
    99  func (c *Client) NewServiceBindingServiceObject(serviceNs string, unstructuredService unstructured.Unstructured, bindingName string) (bindingApi.Service, error) {
   100  	serviceRESTMapping, err := c.GetRestMappingFromUnstructured(unstructuredService)
   101  	if err != nil {
   102  		return bindingApi.Service{}, err
   103  	}
   104  
   105  	var ns *string
   106  	if serviceNs != "" {
   107  		ns = &serviceNs
   108  	}
   109  
   110  	return bindingApi.Service{
   111  		Id: &bindingName, // Id field is helpful if user wants to inject mappings (custom binding data)
   112  		NamespacedRef: bindingApi.NamespacedRef{
   113  			Ref: bindingApi.Ref{
   114  				Group:    serviceRESTMapping.GroupVersionKind.Group,
   115  				Version:  serviceRESTMapping.GroupVersionKind.Version,
   116  				Kind:     serviceRESTMapping.GroupVersionKind.Kind,
   117  				Name:     unstructuredService.GetName(),
   118  				Resource: serviceRESTMapping.Resource.Resource,
   119  			},
   120  			Namespace: ns,
   121  		},
   122  	}, nil
   123  }
   124  
   125  // NewServiceBindingObject returns the bindingApi.ServiceBinding object
   126  func NewServiceBindingObject(
   127  	bindingName string,
   128  	bindAsFiles bool,
   129  	workloadName string,
   130  	namingStrategy string,
   131  	workloadGVK schema.GroupVersionKind,
   132  	mappings []bindingApi.Mapping,
   133  	services []bindingApi.Service,
   134  	status bindingApi.ServiceBindingStatus,
   135  ) *bindingApi.ServiceBinding {
   136  	return &bindingApi.ServiceBinding{
   137  		TypeMeta: v1.TypeMeta{
   138  			APIVersion: bindingApi.GroupVersion.String(),
   139  			Kind:       ServiceBindingKind,
   140  		},
   141  		ObjectMeta: v1.ObjectMeta{
   142  			Name: bindingName,
   143  		},
   144  		Spec: bindingApi.ServiceBindingSpec{
   145  			DetectBindingResources: true,
   146  			BindAsFiles:            bindAsFiles,
   147  			NamingStrategy:         namingStrategy,
   148  			Application: bindingApi.Application{
   149  				Ref: bindingApi.Ref{
   150  					Name:    workloadName,
   151  					Group:   workloadGVK.Group,
   152  					Version: workloadGVK.Version,
   153  					Kind:    workloadGVK.Kind,
   154  				},
   155  			},
   156  			Mappings: mappings,
   157  			Services: services,
   158  		},
   159  		Status: status,
   160  	}
   161  }
   162  
   163  // GetBindingServiceBinding returns a ServiceBinding from group binding.operators.coreos.com/v1alpha1
   164  func (c Client) GetBindingServiceBinding(name string) (bindingApi.ServiceBinding, error) {
   165  	if c.DynamicClient == nil {
   166  		return bindingApi.ServiceBinding{}, nil
   167  	}
   168  
   169  	u, err := c.GetDynamicResource(bindingApi.GroupVersionResource, name)
   170  	if err != nil {
   171  		return bindingApi.ServiceBinding{}, err
   172  	}
   173  
   174  	var result bindingApi.ServiceBinding
   175  	err = ConvertUnstructuredToResource(*u, &result)
   176  	if err != nil {
   177  		return bindingApi.ServiceBinding{}, err
   178  	}
   179  	return result, nil
   180  }
   181  
   182  // GetSpecServiceBinding returns a ServiceBinding from group servicebinding.io/v1alpha3
   183  func (c Client) GetSpecServiceBinding(name string) (specApi.ServiceBinding, error) {
   184  	if c.DynamicClient == nil {
   185  		return specApi.ServiceBinding{}, nil
   186  	}
   187  
   188  	u, err := c.GetDynamicResource(specApi.GroupVersionResource, name)
   189  	if err != nil {
   190  		return specApi.ServiceBinding{}, err
   191  	}
   192  
   193  	var result specApi.ServiceBinding
   194  	err = ConvertUnstructuredToResource(*u, &result)
   195  	if err != nil {
   196  		return specApi.ServiceBinding{}, err
   197  	}
   198  	return result, nil
   199  }
   200  
   201  // ListServiceBindingsFromAllGroups returns the list of ServiceBindings in the cluster
   202  // in the current namespace.
   203  // The first list on the result contains ServiceBinding resources from group servicebinding.io/v1alpha3
   204  // the second list contains ServiceBinding resources from group binding.operators.coreos.com/v1alpha1
   205  func (c Client) ListServiceBindingsFromAllGroups() ([]specApi.ServiceBinding, []bindingApi.ServiceBinding, error) {
   206  	if c.DynamicClient == nil {
   207  		return nil, nil, nil
   208  	}
   209  
   210  	specsU, err := c.ListDynamicResources("", specApi.GroupVersionResource, "")
   211  	var specs specApi.ServiceBindingList
   212  	if err != nil {
   213  		if !kerrors.IsForbidden(err) {
   214  			return nil, nil, err
   215  		}
   216  	} else {
   217  		err = ConvertUnstructuredListToResource(*specsU, &specs)
   218  		if err != nil {
   219  			return nil, nil, err
   220  		}
   221  	}
   222  
   223  	bindingsU, err := c.ListDynamicResources("", bindingApi.GroupVersionResource, "")
   224  	var bindings bindingApi.ServiceBindingList
   225  	if err != nil {
   226  		if !kerrors.IsForbidden(err) {
   227  			return nil, nil, err
   228  		}
   229  	} else {
   230  		err = ConvertUnstructuredListToResource(*bindingsU, &bindings)
   231  		if err != nil {
   232  			return nil, nil, err
   233  		}
   234  	}
   235  
   236  	return specs.Items, bindings.Items, nil
   237  }
   238  
   239  // APIServiceBindingFromBinding returns a common api.ServiceBinding structure
   240  // from a ServiceBinding.binding.operators.coreos.com/v1alpha1
   241  func APIServiceBindingFromBinding(
   242  	binding bindingApi.ServiceBinding,
   243  ) (api.ServiceBinding, error) {
   244  
   245  	var dstSvcs []api.ServiceBindingReference
   246  	for _, srcSvc := range binding.Spec.Services {
   247  		dstSvc := api.ServiceBindingReference{
   248  			Name: srcSvc.Name,
   249  		}
   250  		dstSvc.APIVersion, dstSvc.Kind = schema.GroupVersion{
   251  			Group:   srcSvc.Group,
   252  			Version: srcSvc.Version,
   253  		}.WithKind(srcSvc.Kind).ToAPIVersionAndKind()
   254  		if srcSvc.Namespace != nil {
   255  			dstSvc.Namespace = *srcSvc.Namespace
   256  		}
   257  		dstSvcs = append(dstSvcs, dstSvc)
   258  	}
   259  
   260  	application := binding.Spec.Application
   261  	refToApplication := api.ServiceBindingReference{
   262  		Name:     application.Name,
   263  		Resource: application.Resource,
   264  	}
   265  
   266  	refToApplication.APIVersion, refToApplication.Kind = schema.GroupVersion{
   267  		Group:   application.Group,
   268  		Version: application.Version,
   269  	}.WithKind(application.Kind).ToAPIVersionAndKind()
   270  
   271  	return api.ServiceBinding{
   272  		Name: binding.Name,
   273  		Spec: api.ServiceBindingSpec{
   274  			Application:            refToApplication,
   275  			Services:               dstSvcs,
   276  			DetectBindingResources: binding.Spec.DetectBindingResources,
   277  			BindAsFiles:            binding.Spec.BindAsFiles,
   278  			NamingStrategy:         binding.Spec.NamingStrategy,
   279  		},
   280  	}, nil
   281  }
   282  
   283  // APIServiceBindingFromSpec returns a common api.ServiceBinding structure
   284  // from a ServiceBinding.servicebinding.io/v1alpha3
   285  func APIServiceBindingFromSpec(spec specApi.ServiceBinding) api.ServiceBinding {
   286  
   287  	service := spec.Spec.Service
   288  	refToService := api.ServiceBindingReference{
   289  		APIVersion: service.APIVersion,
   290  		Kind:       service.Kind,
   291  		Name:       service.Name,
   292  	}
   293  
   294  	application := spec.Spec.Workload
   295  	refToApplication := api.ServiceBindingReference{
   296  		APIVersion: application.APIVersion,
   297  		Kind:       application.Kind,
   298  		Name:       application.Name,
   299  	}
   300  
   301  	return api.ServiceBinding{
   302  		Name: spec.Name,
   303  		Spec: api.ServiceBindingSpec{
   304  			Application:            refToApplication,
   305  			Services:               []api.ServiceBindingReference{refToService},
   306  			DetectBindingResources: false,
   307  			BindAsFiles:            true,
   308  		},
   309  	}
   310  }
   311  
   312  // GetWorkloadKinds returns all the workload kinds present in the cluster
   313  // It considers that all native resources are present and tests only for custom resources
   314  // Returns an array of Kinds and an array of GVKs
   315  func (c Client) GetWorkloadKinds() ([]string, []schema.GroupVersionKind, error) {
   316  	var allWorkloadsKinds = []schema.GroupVersionKind{}
   317  	var options []string
   318  	for _, gvk := range NativeWorkloadKinds {
   319  		options = append(options, gvk.Kind)
   320  		allWorkloadsKinds = append(allWorkloadsKinds, gvk)
   321  	}
   322  
   323  	// Test for each custom workload kind if it exists in the cluster
   324  	for _, gvk := range CustomWorkloadKinds {
   325  		_, err := c.GetGVRFromGVK(gvk)
   326  		if err != nil {
   327  			// This is sufficient to test if resource exists in cluster
   328  			if meta.IsNoMatchError(err) {
   329  				continue
   330  			}
   331  			return nil, nil, err
   332  		}
   333  		options = append(options, gvk.Kind)
   334  		allWorkloadsKinds = append(allWorkloadsKinds, gvk)
   335  	}
   336  	return options, allWorkloadsKinds, nil
   337  }
   338  
   339  func mappingContainsBKS(bindableObjects []*meta.RESTMapping, bks bindingApi.BindableKindsStatus) bool {
   340  	var gkAlreadyAdded bool
   341  	// check every GroupKind only once
   342  	for _, bo := range bindableObjects {
   343  		if bo.GroupVersionKind.Group == bks.Group && bo.GroupVersionKind.Kind == bks.Kind {
   344  			gkAlreadyAdded = true
   345  			break
   346  		}
   347  	}
   348  	return gkAlreadyAdded
   349  }
   350  

View as plain text