...

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

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

     1  package binding
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  
     7  	"k8s.io/klog"
     8  
     9  	bindingApis "github.com/redhat-developer/service-binding-operator/apis"
    10  	bindingApi "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
    11  	specApi "github.com/redhat-developer/service-binding-operator/apis/spec/v1alpha3"
    12  
    13  	"github.com/redhat-developer/odo/pkg/project"
    14  
    15  	"github.com/devfile/library/v2/pkg/devfile/parser"
    16  	devfilefs "github.com/devfile/library/v2/pkg/testingutil/filesystem"
    17  	"gopkg.in/yaml.v2"
    18  	appsv1 "k8s.io/api/apps/v1"
    19  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    20  	"k8s.io/apimachinery/pkg/api/meta"
    21  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    22  	"k8s.io/apimachinery/pkg/runtime"
    23  
    24  	devfilev1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    25  	parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
    26  
    27  	"github.com/redhat-developer/odo/pkg/api"
    28  	"github.com/redhat-developer/odo/pkg/binding/asker"
    29  	backendpkg "github.com/redhat-developer/odo/pkg/binding/backend"
    30  	"github.com/redhat-developer/odo/pkg/kclient"
    31  	"github.com/redhat-developer/odo/pkg/libdevfile"
    32  	clierrors "github.com/redhat-developer/odo/pkg/odo/cli/errors"
    33  )
    34  
    35  type BindingClient struct {
    36  	// Backends
    37  	flagsBackend       *backendpkg.FlagsBackend
    38  	interactiveBackend *backendpkg.InteractiveBackend
    39  
    40  	// Clients
    41  	kubernetesClient kclient.ClientInterface
    42  }
    43  
    44  var _ Client = (*BindingClient)(nil)
    45  
    46  func NewBindingClient(projectClient project.Client, kubernetesClient kclient.ClientInterface) *BindingClient {
    47  	// We create the asker client and the backends here and not at the CLI level, as we want to hide these details to the CLI
    48  	askerClient := asker.NewSurveyAsker()
    49  	return &BindingClient{
    50  		flagsBackend:       backendpkg.NewFlagsBackend(),
    51  		interactiveBackend: backendpkg.NewInteractiveBackend(askerClient, projectClient, kubernetesClient),
    52  		kubernetesClient:   kubernetesClient,
    53  	}
    54  }
    55  
    56  // GetFlags gets the flag specific to add binding operation so that it can correctly decide on the backend to be used
    57  // It ignores all the flags except the ones specific to add binding operation, for e.g. verbosity flag
    58  func (o *BindingClient) GetFlags(flags map[string]string) map[string]string {
    59  	bindingFlags := map[string]string{}
    60  	for flag, value := range flags {
    61  		if flag == backendpkg.FLAG_NAME ||
    62  			flag == backendpkg.FLAG_WORKLOAD ||
    63  			flag == backendpkg.FLAG_SERVICE_NAMESPACE ||
    64  			flag == backendpkg.FLAG_SERVICE ||
    65  			flag == backendpkg.FLAG_BIND_AS_FILES ||
    66  			flag == backendpkg.FLAG_NAMING_STRATEGY {
    67  			bindingFlags[flag] = value
    68  		}
    69  	}
    70  	return bindingFlags
    71  }
    72  
    73  func (o *BindingClient) GetServiceInstances(namespace string) (map[string]unstructured.Unstructured, error) {
    74  	err := o.checkServiceBindingOperatorInstalled()
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	// Get the BindableKinds/bindable-kinds object
    80  	bindableKind, err := o.kubernetesClient.GetBindableKinds()
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	// get a list of restMappings of all the GVKs present in bindableKind's Status
    86  	bindableKindRestMappings, err := o.kubernetesClient.GetBindableKindStatusRestMapping(bindableKind.Status)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	var bindableObjectMap = map[string]unstructured.Unstructured{}
    92  	for _, restMapping := range bindableKindRestMappings {
    93  		// TODO: Debug into why List returns all the versions instead of the GVR version
    94  		// List all the instances of the restMapping object
    95  		resources, err := o.kubernetesClient.ListDynamicResources(namespace, restMapping.Resource, "")
    96  		if err != nil {
    97  			if kerrors.IsNotFound(err) || kerrors.IsForbidden(err) {
    98  				// Assume the namespace is deleted or being terminated, hence user can't list its resources
    99  				klog.V(3).Infoln(err)
   100  				continue
   101  			}
   102  			return nil, err
   103  		}
   104  
   105  		for _, item := range resources.Items {
   106  			// format: `<name> (<kind>.<group>)`
   107  			serviceName := fmt.Sprintf("%s (%s.%s)", item.GetName(), item.GetKind(), item.GroupVersionKind().Group)
   108  			bindableObjectMap[serviceName] = item
   109  		}
   110  
   111  	}
   112  
   113  	return bindableObjectMap, nil
   114  }
   115  
   116  // GetBindingsFromDevfile returns all ServiceBinding resources declared as Kubernertes component from a Devfile
   117  // from group binding.operators.coreos.com/v1alpha1 or servicebinding.io/v1alpha3
   118  // The function also returns status information of the binding in the cluster, if accessible, or a warning if the cluster is not accessible
   119  func (o *BindingClient) GetBindingsFromDevfile(devfileObj parser.DevfileObj, context string) ([]api.ServiceBinding, error) {
   120  	result := []api.ServiceBinding{}
   121  	kubeComponents, err := devfileObj.Data.GetComponents(parsercommon.DevfileOptions{
   122  		ComponentOptions: parsercommon.ComponentOptions{
   123  			ComponentType: devfilev1alpha2.KubernetesComponentType,
   124  		},
   125  	})
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	ocpComponents, err := devfileObj.Data.GetComponents(parsercommon.DevfileOptions{
   130  		ComponentOptions: parsercommon.ComponentOptions{
   131  			ComponentType: devfilev1alpha2.OpenshiftComponentType,
   132  		},
   133  	})
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	allComponents := make([]devfilev1alpha2.Component, 0, len(kubeComponents)+len(ocpComponents))
   139  	allComponents = append(allComponents, kubeComponents...)
   140  	allComponents = append(allComponents, ocpComponents...)
   141  
   142  	var warning error
   143  	for _, component := range allComponents {
   144  		strCRD, err := libdevfile.GetK8sManifestsWithVariablesSubstituted(devfileObj, component.Name, context, devfilefs.DefaultFs{})
   145  		if err != nil {
   146  			return nil, err
   147  		}
   148  
   149  		u := unstructured.Unstructured{}
   150  		if err := yaml.Unmarshal([]byte(strCRD), &u.Object); err != nil {
   151  			return nil, err
   152  		}
   153  
   154  		switch u.GetObjectKind().GroupVersionKind() {
   155  		case bindingApi.GroupVersionKind:
   156  
   157  			var sbo bindingApi.ServiceBinding
   158  			err := kclient.ConvertUnstructuredToResource(u, &sbo)
   159  			if err != nil {
   160  				return nil, err
   161  			}
   162  
   163  			sb, err := kclient.APIServiceBindingFromBinding(sbo)
   164  			if err != nil {
   165  				return nil, err
   166  			}
   167  			sb.Status, err = o.getStatusFromBinding(sb.Name)
   168  			if err != nil {
   169  				warning = clierrors.NewWarning(kclient.NewNoConnectionError().Error(), err)
   170  			}
   171  
   172  			result = append(result, sb)
   173  
   174  		case specApi.GroupVersion.WithKind("ServiceBinding"):
   175  
   176  			var sbc specApi.ServiceBinding
   177  			err := kclient.ConvertUnstructuredToResource(u, &sbc)
   178  			if err != nil {
   179  				return nil, err
   180  			}
   181  
   182  			sb := kclient.APIServiceBindingFromSpec(sbc)
   183  			sb.Status, err = o.getStatusFromSpec(sb.Name)
   184  			if err != nil {
   185  				warning = clierrors.NewWarning(kclient.NewNoConnectionError().Error(), err)
   186  			}
   187  
   188  			result = append(result, sb)
   189  
   190  		}
   191  	}
   192  	return result, warning
   193  }
   194  
   195  // GetBindingFromCluster returns the ServiceBinding resource with the given name
   196  // from the cluster, from group binding.operators.coreos.com/v1alpha1 or servicebinding.io/v1alpha3
   197  func (o *BindingClient) GetBindingFromCluster(name string) (api.ServiceBinding, error) {
   198  	bindingSB, err := o.kubernetesClient.GetBindingServiceBinding(name)
   199  	if err == nil {
   200  		var sb api.ServiceBinding
   201  		sb, err = kclient.APIServiceBindingFromBinding(bindingSB)
   202  		if err != nil {
   203  			return api.ServiceBinding{}, err
   204  		}
   205  		sb.Status, err = o.getStatusFromBinding(bindingSB.Name)
   206  		if err != nil {
   207  			return api.ServiceBinding{}, err
   208  		}
   209  		return sb, nil
   210  	}
   211  	if err != nil && !kerrors.IsNotFound(err) {
   212  		return api.ServiceBinding{}, err
   213  	}
   214  
   215  	specSB, err := o.kubernetesClient.GetSpecServiceBinding(name)
   216  	if err == nil {
   217  		sb := kclient.APIServiceBindingFromSpec(specSB)
   218  		sb.Status, err = o.getStatusFromSpec(specSB.Name)
   219  		if err != nil {
   220  			return api.ServiceBinding{}, err
   221  		}
   222  		return sb, nil
   223  	}
   224  
   225  	// In case of notFound error, this time we return the error
   226  	if kerrors.IsNotFound(err) {
   227  		return api.ServiceBinding{}, fmt.Errorf("ServiceBinding %q not found", name)
   228  	}
   229  	return api.ServiceBinding{}, err
   230  }
   231  
   232  // getStatusFromBinding returns status information from a ServiceBinding in the cluster
   233  // from group binding.operators.coreos.com/v1alpha1
   234  func (o *BindingClient) getStatusFromBinding(name string) (*api.ServiceBindingStatus, error) {
   235  	if o.kubernetesClient == nil {
   236  		return nil, nil
   237  	}
   238  	bindingSB, err := o.kubernetesClient.GetBindingServiceBinding(name)
   239  	if err != nil {
   240  		if kerrors.IsNotFound(err) {
   241  			return nil, nil
   242  		}
   243  		return nil, err
   244  	}
   245  
   246  	if injected := meta.IsStatusConditionTrue(bindingSB.Status.Conditions, bindingApis.InjectionReady); !injected {
   247  		return nil, nil
   248  	}
   249  
   250  	secretName := bindingSB.Status.Secret
   251  	secret, err := o.kubernetesClient.GetSecret(secretName, o.kubernetesClient.GetCurrentNamespace())
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	bindings := make([]string, 0, len(secret.Data))
   257  	if bindingSB.Spec.BindAsFiles {
   258  		for k := range secret.Data {
   259  			bindingName := filepath.ToSlash(filepath.Join("${SERVICE_BINDING_ROOT}", name, k))
   260  			bindings = append(bindings, bindingName)
   261  		}
   262  		return &api.ServiceBindingStatus{
   263  			BindingFiles: bindings,
   264  		}, nil
   265  	}
   266  
   267  	for k := range secret.Data {
   268  		bindings = append(bindings, k)
   269  	}
   270  	return &api.ServiceBindingStatus{
   271  		BindingEnvVars: bindings,
   272  	}, nil
   273  }
   274  
   275  // getStatusFromSpec returns status information from a ServiceBinding in the cluster
   276  // from group servicebinding.io/v1alpha3
   277  func (o *BindingClient) getStatusFromSpec(name string) (*api.ServiceBindingStatus, error) {
   278  	specSB, err := o.kubernetesClient.GetSpecServiceBinding(name)
   279  	if err != nil {
   280  		if kerrors.IsNotFound(err) {
   281  			return nil, nil
   282  		}
   283  		return nil, err
   284  	}
   285  
   286  	if injected := meta.IsStatusConditionTrue(specSB.Status.Conditions, bindingApis.InjectionReady); !injected {
   287  		return nil, nil
   288  	}
   289  
   290  	if specSB.Status.Binding == nil {
   291  		return nil, nil
   292  	}
   293  	secretName := specSB.Status.Binding.Name
   294  	secret, err := o.kubernetesClient.GetSecret(secretName, o.kubernetesClient.GetCurrentNamespace())
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  	bindingFiles := make([]string, 0, len(secret.Data))
   299  	bindingEnvVars := make([]string, 0, len(specSB.Spec.Env))
   300  	for k := range secret.Data {
   301  		bindingName := filepath.ToSlash(filepath.Join("${SERVICE_BINDING_ROOT}", name, k))
   302  		bindingFiles = append(bindingFiles, bindingName)
   303  	}
   304  	for _, env := range specSB.Spec.Env {
   305  		bindingEnvVars = append(bindingEnvVars, env.Name)
   306  	}
   307  	return &api.ServiceBindingStatus{
   308  		BindingFiles:   bindingFiles,
   309  		BindingEnvVars: bindingEnvVars,
   310  	}, nil
   311  }
   312  
   313  func (o *BindingClient) checkServiceBindingOperatorInstalled() error {
   314  	isServiceBindingInstalled, err := o.kubernetesClient.IsServiceBindingSupported()
   315  	if err != nil {
   316  		return err
   317  	}
   318  	if !isServiceBindingInstalled {
   319  		//revive:disable:error-strings This is a top-level error message displayed as is to the end user
   320  		return fmt.Errorf("Service Binding Operator is not installed on the cluster, please ensure it is installed before proceeding. " +
   321  			"See installation instructions: https://odo.dev/docs/command-reference/add-binding#installing-the-service-binding-operator")
   322  		//revive:enable:error-strings
   323  	}
   324  	return nil
   325  }
   326  
   327  func (o *BindingClient) CheckServiceBindingsInjectionDone(componentName string, appName string) (bool, error) {
   328  
   329  	deployment, err := o.kubernetesClient.GetOneDeployment(componentName, appName, true)
   330  	if err != nil {
   331  		// If not deployment yet => all bindings are done
   332  		if _, ok := err.(*kclient.DeploymentNotFoundError); ok {
   333  			return true, nil
   334  		}
   335  		return false, err
   336  	}
   337  	deploymentName := deployment.GetName()
   338  
   339  	specList, bindingList, err := o.kubernetesClient.ListServiceBindingsFromAllGroups()
   340  	if err != nil {
   341  		// If ServiceBinding kind is not registered => all bindings are done
   342  		if runtime.IsNotRegisteredError(err) {
   343  			return true, nil
   344  		}
   345  		return false, err
   346  	}
   347  
   348  	for _, binding := range bindingList {
   349  		app := binding.Spec.Application
   350  		if app.Group != appsv1.SchemeGroupVersion.Group ||
   351  			app.Version != appsv1.SchemeGroupVersion.Version ||
   352  			(app.Kind != "Deployment" && app.Resource != "deployments") {
   353  			continue
   354  		}
   355  		if app.Name != deploymentName {
   356  			continue
   357  		}
   358  		if injected := meta.IsStatusConditionTrue(binding.Status.Conditions, bindingApis.InjectionReady); !injected {
   359  			return false, nil
   360  		}
   361  	}
   362  
   363  	for _, binding := range specList {
   364  		app := binding.Spec.Workload
   365  		if app.APIVersion != appsv1.SchemeGroupVersion.String() ||
   366  			app.Kind != "Deployment" {
   367  			continue
   368  		}
   369  		if app.Name != deploymentName {
   370  			continue
   371  		}
   372  		if injected := meta.IsStatusConditionTrue(binding.Status.Conditions, bindingApis.InjectionReady); !injected {
   373  			return false, nil
   374  		}
   375  	}
   376  
   377  	return true, nil
   378  }
   379  

View as plain text