...

Source file src/github.com/redhat-developer/odo/pkg/dev/kubedev/components.go

Documentation: github.com/redhat-developer/odo/pkg/dev/kubedev

     1  package kubedev
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"path/filepath"
     8  	"reflect"
     9  	"strings"
    10  
    11  	"golang.org/x/sync/errgroup"
    12  
    13  	devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    14  	"github.com/devfile/library/v2/pkg/devfile/generator"
    15  	"github.com/devfile/library/v2/pkg/devfile/parser"
    16  	parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
    17  	devfilefs "github.com/devfile/library/v2/pkg/testingutil/filesystem"
    18  	dfutil "github.com/devfile/library/v2/pkg/util"
    19  
    20  	"github.com/redhat-developer/odo/pkg/component"
    21  	"github.com/redhat-developer/odo/pkg/dev/common"
    22  	"github.com/redhat-developer/odo/pkg/dev/kubedev/storage"
    23  	"github.com/redhat-developer/odo/pkg/dev/kubedev/utils"
    24  	"github.com/redhat-developer/odo/pkg/devfile/image"
    25  	"github.com/redhat-developer/odo/pkg/kclient"
    26  	odolabels "github.com/redhat-developer/odo/pkg/labels"
    27  	"github.com/redhat-developer/odo/pkg/libdevfile"
    28  	"github.com/redhat-developer/odo/pkg/log"
    29  	odocontext "github.com/redhat-developer/odo/pkg/odo/context"
    30  	"github.com/redhat-developer/odo/pkg/service"
    31  	storagepkg "github.com/redhat-developer/odo/pkg/storage"
    32  	"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
    33  	"github.com/redhat-developer/odo/pkg/util"
    34  	"github.com/redhat-developer/odo/pkg/watch"
    35  
    36  	appsv1 "k8s.io/api/apps/v1"
    37  	corev1 "k8s.io/api/core/v1"
    38  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    39  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    40  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    41  	"k8s.io/klog"
    42  	"k8s.io/utils/pointer"
    43  )
    44  
    45  // createComponents creates the components into the cluster
    46  // returns true if the pod is created
    47  func (o *DevClient) createComponents(ctx context.Context, parameters common.PushParameters, componentStatus *watch.ComponentStatus) (bool, error) {
    48  	var (
    49  		appName       = odocontext.GetApplication(ctx)
    50  		componentName = odocontext.GetComponentName(ctx)
    51  	)
    52  
    53  	// preliminary checks
    54  	err := dfutil.ValidateK8sResourceName("component name", componentName)
    55  	if err != nil {
    56  		return false, err
    57  	}
    58  
    59  	err = dfutil.ValidateK8sResourceName("component namespace", o.kubernetesClient.GetCurrentNamespace())
    60  	if err != nil {
    61  		return false, err
    62  	}
    63  
    64  	if componentStatus.GetState() == watch.StateSyncOutdated {
    65  		// Clear the cache of image components already applied, hence forcing image components to be reapplied.
    66  		componentStatus.ImageComponentsAutoApplied = make(map[string]devfilev1.ImageComponent)
    67  	}
    68  
    69  	klog.V(4).Infof("component state: %q\n", componentStatus.GetState())
    70  	err = o.buildPushAutoImageComponents(ctx, o.filesystem, parameters.Devfile, componentStatus)
    71  	if err != nil {
    72  		return false, err
    73  	}
    74  
    75  	var deployment *appsv1.Deployment
    76  	deployment, o.deploymentExists, err = o.getComponentDeployment(ctx)
    77  	if err != nil {
    78  		return false, err
    79  	}
    80  
    81  	if componentStatus.GetState() != watch.StateWaitDeployment && componentStatus.GetState() != watch.StateReady {
    82  		log.SpinnerNoSpin("Waiting for Kubernetes resources")
    83  	}
    84  
    85  	// Set the mode to Dev since we are using "odo dev" here
    86  	runtime := component.GetComponentRuntimeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata())
    87  	labels := odolabels.GetLabels(componentName, appName, runtime, odolabels.ComponentDevMode, false)
    88  
    89  	var updated bool
    90  	deployment, updated, err = o.createOrUpdateComponent(ctx, parameters, o.deploymentExists, libdevfile.DevfileCommands{
    91  		BuildCmd: parameters.StartOptions.BuildCommand,
    92  		RunCmd:   parameters.StartOptions.RunCommand,
    93  		DebugCmd: parameters.StartOptions.DebugCommand,
    94  	}, deployment)
    95  	if err != nil {
    96  		return false, fmt.Errorf("unable to create or update component: %w", err)
    97  	}
    98  	ownerReference := generator.GetOwnerReference(deployment)
    99  
   100  	// Delete remote resources that are not present in the Devfile
   101  	selector := odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode, false)
   102  
   103  	objectsToRemove, serviceBindingSecretsToRemove, err := o.getRemoteResourcesNotPresentInDevfile(ctx, parameters, selector)
   104  	if err != nil {
   105  		return false, fmt.Errorf("unable to determine resources to delete: %w", err)
   106  	}
   107  
   108  	err = o.deleteRemoteResources(objectsToRemove)
   109  	if err != nil {
   110  		return false, fmt.Errorf("unable to delete remote resources: %w", err)
   111  	}
   112  
   113  	// this is mainly useful when the Service Binding Operator is not installed;
   114  	// and the service binding secrets must be deleted manually since they are created by odo
   115  	if len(serviceBindingSecretsToRemove) != 0 {
   116  		err = o.deleteServiceBindingSecrets(serviceBindingSecretsToRemove, deployment)
   117  		if err != nil {
   118  			return false, fmt.Errorf("unable to delete service binding secrets: %w", err)
   119  		}
   120  	}
   121  
   122  	// Create all the K8s components defined in the devfile
   123  	_, err = o.pushDevfileKubernetesComponents(ctx, parameters, labels, odolabels.ComponentDevMode, ownerReference)
   124  	if err != nil {
   125  		return false, err
   126  	}
   127  
   128  	err = o.updatePVCsOwnerReferences(ctx, ownerReference)
   129  	if err != nil {
   130  		return false, err
   131  	}
   132  
   133  	if updated {
   134  		klog.V(4).Infof("Deployment has been updated to generation %d. Waiting new event...\n", deployment.GetGeneration())
   135  		componentStatus.SetState(watch.StateWaitDeployment)
   136  		return false, nil
   137  	}
   138  
   139  	numberReplicas := deployment.Status.ReadyReplicas
   140  	if numberReplicas != 1 {
   141  		klog.V(4).Infof("Deployment has %d ready replicas. Waiting new event...\n", numberReplicas)
   142  		componentStatus.SetState(watch.StateWaitDeployment)
   143  		return false, nil
   144  	}
   145  
   146  	injected, err := o.bindingClient.CheckServiceBindingsInjectionDone(componentName, appName)
   147  	if err != nil {
   148  		return false, err
   149  	}
   150  
   151  	if !injected {
   152  		klog.V(4).Infof("Waiting for all service bindings to be injected...\n")
   153  		return false, errors.New("some servicebindings are not injected")
   154  	}
   155  
   156  	// Check if endpoints changed in Devfile
   157  	o.portsToForward, err = libdevfile.GetDevfileContainerEndpointMapping(parameters.Devfile, parameters.StartOptions.Debug)
   158  	if err != nil {
   159  		return false, err
   160  	}
   161  	o.portsChanged = !reflect.DeepEqual(o.portsToForward, o.portForwardClient.GetForwardedPorts())
   162  
   163  	if componentStatus.GetState() == watch.StateReady && !o.portsChanged {
   164  		// If the deployment is already in Ready State, no need to continue
   165  		return false, nil
   166  	}
   167  	return true, nil
   168  }
   169  
   170  func (o *DevClient) buildPushAutoImageComponents(ctx context.Context, fs filesystem.Filesystem, devfileObj parser.DevfileObj, compStatus *watch.ComponentStatus) error {
   171  	components, err := libdevfile.GetImageComponentsToPushAutomatically(devfileObj)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	for _, c := range components {
   177  		if c.Image == nil {
   178  			return fmt.Errorf("component %q should be an Image Component", c.Name)
   179  		}
   180  		alreadyApplied, ok := compStatus.ImageComponentsAutoApplied[c.Name]
   181  		if ok && reflect.DeepEqual(*c.Image, alreadyApplied) {
   182  			klog.V(1).Infof("Skipping image component %q; already applied and not changed", c.Name)
   183  			continue
   184  		}
   185  
   186  		err = image.BuildPushSpecificImage(ctx, image.SelectBackend(ctx), fs, c, true)
   187  		if err != nil {
   188  			return err
   189  		}
   190  		compStatus.ImageComponentsAutoApplied[c.Name] = *c.Image
   191  	}
   192  
   193  	// Remove keys that might no longer be valid
   194  	devfileHasCompFn := func(n string) bool {
   195  		for _, c := range components {
   196  			if c.Name == n {
   197  				return true
   198  			}
   199  		}
   200  		return false
   201  	}
   202  	for n := range compStatus.ImageComponentsAutoApplied {
   203  		if !devfileHasCompFn(n) {
   204  			delete(compStatus.ImageComponentsAutoApplied, n)
   205  		}
   206  	}
   207  
   208  	return nil
   209  }
   210  
   211  // getComponentDeployment returns the deployment associated with the component, if deployed
   212  // and indicate if the deployment has been found
   213  func (o *DevClient) getComponentDeployment(ctx context.Context) (*appsv1.Deployment, bool, error) {
   214  	var (
   215  		componentName = odocontext.GetComponentName(ctx)
   216  		appName       = odocontext.GetApplication(ctx)
   217  	)
   218  
   219  	// Get the Dev deployment:
   220  	// Since `odo deploy` can theoretically deploy a deployment as well with the same instance name
   221  	// we make sure that we are retrieving the deployment with the Dev mode, NOT Deploy.
   222  	selectorLabels := odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode, true)
   223  	deployment, err := o.kubernetesClient.GetOneDeploymentFromSelector(selectorLabels)
   224  
   225  	if err != nil {
   226  		if _, ok := err.(*kclient.DeploymentNotFoundError); !ok {
   227  			return nil, false, fmt.Errorf("unable to determine if component %s exists: %w", componentName, err)
   228  		}
   229  	}
   230  	componentExists := deployment != nil
   231  	return deployment, componentExists, nil
   232  }
   233  
   234  // createOrUpdateComponent creates the deployment or updates it if it already exists
   235  // with the expected spec.
   236  // Returns the new deployment and if the generation of the deployment has been updated
   237  func (o *DevClient) createOrUpdateComponent(
   238  	ctx context.Context,
   239  	parameters common.PushParameters,
   240  	componentExists bool,
   241  	commands libdevfile.DevfileCommands,
   242  	deployment *appsv1.Deployment,
   243  ) (*appsv1.Deployment, bool, error) {
   244  
   245  	var (
   246  		appName       = odocontext.GetApplication(ctx)
   247  		componentName = odocontext.GetComponentName(ctx)
   248  		devfilePath   = odocontext.GetDevfilePath(ctx)
   249  		path          = filepath.Dir(devfilePath)
   250  	)
   251  
   252  	runtime := component.GetComponentRuntimeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata())
   253  
   254  	// Set the labels
   255  	labels := odolabels.GetLabels(componentName, appName, runtime, odolabels.ComponentDevMode, true)
   256  
   257  	annotations := make(map[string]string)
   258  	odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata()))
   259  	odolabels.AddCommonAnnotations(annotations)
   260  	klog.V(4).Infof("We are deploying these annotations: %s", annotations)
   261  
   262  	deploymentObjectMeta, err := o.generateDeploymentObjectMeta(ctx, deployment, labels, annotations)
   263  	if err != nil {
   264  		return nil, false, err
   265  	}
   266  
   267  	policy, err := o.kubernetesClient.GetCurrentNamespacePolicy()
   268  	if err != nil {
   269  		return nil, false, err
   270  	}
   271  	podTemplateSpec, err := generator.GetPodTemplateSpec(parameters.Devfile, generator.PodTemplateParams{
   272  		ObjectMeta:                 deploymentObjectMeta,
   273  		PodSecurityAdmissionPolicy: policy,
   274  	})
   275  	if err != nil {
   276  		return nil, false, err
   277  	}
   278  	containers := podTemplateSpec.Spec.Containers
   279  	if len(containers) == 0 {
   280  		return nil, false, fmt.Errorf("no valid components found in the devfile")
   281  	}
   282  
   283  	initContainers := podTemplateSpec.Spec.InitContainers
   284  
   285  	containers, err = utils.UpdateContainersEntrypointsIfNeeded(parameters.Devfile, containers, commands.BuildCmd, commands.RunCmd, commands.DebugCmd)
   286  	if err != nil {
   287  		return nil, false, err
   288  	}
   289  
   290  	// Returns the volumes to add to the PodTemplate and adds volumeMounts to the containers and initContainers
   291  	volumes, err := o.buildVolumes(ctx, parameters, containers, initContainers)
   292  	if err != nil {
   293  		return nil, false, err
   294  	}
   295  	podTemplateSpec.Spec.Volumes = volumes
   296  
   297  	selectorLabels := map[string]string{
   298  		"component": componentName,
   299  	}
   300  
   301  	deployParams := generator.DeploymentParams{
   302  		TypeMeta:          generator.GetTypeMeta(kclient.DeploymentKind, kclient.DeploymentAPIVersion),
   303  		ObjectMeta:        deploymentObjectMeta,
   304  		PodTemplateSpec:   podTemplateSpec,
   305  		PodSelectorLabels: selectorLabels,
   306  		Replicas:          pointer.Int32(1),
   307  	}
   308  
   309  	// Save generation to check if deployment is updated later
   310  	var originalGeneration int64 = 0
   311  	if deployment != nil {
   312  		originalGeneration = deployment.GetGeneration()
   313  	}
   314  
   315  	deployment, err = generator.GetDeployment(parameters.Devfile, deployParams)
   316  	if err != nil {
   317  		return nil, false, err
   318  	}
   319  	if deployment.Annotations == nil {
   320  		deployment.Annotations = make(map[string]string)
   321  	}
   322  
   323  	if vcsUri := util.GetGitOriginPath(path); vcsUri != "" {
   324  		deployment.Annotations["app.openshift.io/vcs-uri"] = vcsUri
   325  	}
   326  
   327  	// add the annotations to the service for linking
   328  	serviceAnnotations := make(map[string]string)
   329  	serviceAnnotations["service.binding/backend_ip"] = "path={.spec.clusterIP}"
   330  	serviceAnnotations["service.binding/backend_port"] = "path={.spec.ports},elementType=sliceOfMaps,sourceKey=name,sourceValue=port"
   331  
   332  	serviceName, err := util.NamespaceKubernetesObjectWithTrim(componentName, appName, 63)
   333  	if err != nil {
   334  		return nil, false, err
   335  	}
   336  	serviceObjectMeta := generator.GetObjectMeta(serviceName, o.kubernetesClient.GetCurrentNamespace(), labels, serviceAnnotations)
   337  	serviceParams := generator.ServiceParams{
   338  		ObjectMeta:     serviceObjectMeta,
   339  		SelectorLabels: selectorLabels,
   340  	}
   341  	svc, err := generator.GetService(parameters.Devfile, serviceParams, parsercommon.DevfileOptions{})
   342  
   343  	if err != nil {
   344  		return nil, false, err
   345  	}
   346  	klog.V(2).Infof("Creating deployment %v", deployment.Spec.Template.GetName())
   347  	klog.V(2).Infof("The component name is %v", componentName)
   348  	if componentExists {
   349  		// If the component already exists, get the resource version of the deploy before updating
   350  		klog.V(2).Info("The component already exists, attempting to update it")
   351  		if o.kubernetesClient.IsSSASupported() {
   352  			klog.V(4).Info("Applying deployment")
   353  			deployment, err = o.kubernetesClient.ApplyDeployment(*deployment)
   354  		} else {
   355  			klog.V(4).Info("Updating deployment")
   356  			deployment, err = o.kubernetesClient.UpdateDeployment(*deployment)
   357  		}
   358  		if err != nil {
   359  			return nil, false, err
   360  		}
   361  		klog.V(2).Infof("Successfully updated component %v", componentName)
   362  		ownerReference := generator.GetOwnerReference(deployment)
   363  		err = o.createOrUpdateServiceForComponent(ctx, svc, ownerReference)
   364  		if err != nil {
   365  			return nil, false, err
   366  		}
   367  	} else {
   368  		if o.kubernetesClient.IsSSASupported() {
   369  			deployment, err = o.kubernetesClient.ApplyDeployment(*deployment)
   370  		} else {
   371  			deployment, err = o.kubernetesClient.CreateDeployment(*deployment)
   372  		}
   373  
   374  		if err != nil {
   375  			return nil, false, err
   376  		}
   377  
   378  		klog.V(2).Infof("Successfully created component %v", componentName)
   379  		if len(svc.Spec.Ports) > 0 {
   380  			ownerReference := generator.GetOwnerReference(deployment)
   381  			originOwnerRefs := svc.OwnerReferences
   382  			err = o.kubernetesClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error {
   383  				svc.OwnerReferences = append(originOwnerRefs, ownerRef)
   384  				_, err = o.kubernetesClient.CreateService(*svc)
   385  				return err
   386  			})
   387  			if err != nil {
   388  				return nil, false, err
   389  			}
   390  			klog.V(2).Infof("Successfully created Service for component %s", componentName)
   391  		}
   392  
   393  	}
   394  	newGeneration := deployment.GetGeneration()
   395  
   396  	return deployment, newGeneration != originalGeneration, nil
   397  }
   398  
   399  // getRemoteResourcesNotPresentInDevfile compares the list of Devfile K8s component and remote K8s resources
   400  // and returns a list of the remote resources not present in the Devfile and in case the SBO is not installed, a list of service binding secrets that must be deleted;
   401  // it ignores the core components (such as deployments, svc, pods; all resources with `component:<something>` label)
   402  func (o DevClient) getRemoteResourcesNotPresentInDevfile(ctx context.Context, parameters common.PushParameters, selector string) (objectsToRemove, serviceBindingSecretsToRemove []unstructured.Unstructured, err error) {
   403  	var (
   404  		devfilePath = odocontext.GetDevfilePath(ctx)
   405  		path        = filepath.Dir(devfilePath)
   406  	)
   407  
   408  	currentNamespace := o.kubernetesClient.GetCurrentNamespace()
   409  	allRemoteK8sResources, err := o.kubernetesClient.GetAllResourcesFromSelector(selector, currentNamespace)
   410  	if err != nil {
   411  		return nil, nil, fmt.Errorf("unable to fetch remote resources: %w", err)
   412  	}
   413  
   414  	var remoteK8sResources []unstructured.Unstructured
   415  	// Filter core components
   416  	for _, remoteK := range allRemoteK8sResources {
   417  		if !odolabels.IsCoreComponent(remoteK.GetLabels()) {
   418  			// ignore the resources that are already set for deletion
   419  			// ignore the resources that do not have projecttype annotation set; they will be the resources that are not created by odo
   420  			// for e.g. PodMetrics is a resource that is created if Monitoring is enabled on OCP;
   421  			// this resource has the same label as it's deployment, it has no owner reference; but it does not have the annotation either
   422  			if remoteK.GetDeletionTimestamp() != nil && !odolabels.IsProjectTypeSetInAnnotations(remoteK.GetAnnotations()) {
   423  				continue
   424  			}
   425  			remoteK8sResources = append(remoteK8sResources, remoteK)
   426  		}
   427  	}
   428  
   429  	var devfileK8sResources []devfilev1.Component
   430  	devfileK8sResources, err = libdevfile.GetK8sAndOcComponentsToPush(parameters.Devfile, true)
   431  	if err != nil {
   432  		return nil, nil, fmt.Errorf("unable to obtain resources from the Devfile: %w", err)
   433  	}
   434  
   435  	// convert all devfileK8sResources to unstructured data
   436  	var devfileK8sResourcesUnstructured []unstructured.Unstructured
   437  	for _, devfileK := range devfileK8sResources {
   438  		var devfileKUnstructuredList []unstructured.Unstructured
   439  		devfileKUnstructuredList, err = libdevfile.GetK8sComponentAsUnstructuredList(parameters.Devfile, devfileK.Name, path, devfilefs.DefaultFs{})
   440  		if err != nil {
   441  			return nil, nil, fmt.Errorf("unable to read the resource: %w", err)
   442  		}
   443  		devfileK8sResourcesUnstructured = append(devfileK8sResourcesUnstructured, devfileKUnstructuredList...)
   444  	}
   445  
   446  	isSBOSupported, err := o.kubernetesClient.IsServiceBindingSupported()
   447  	if err != nil {
   448  		return nil, nil, fmt.Errorf("error in determining support for the Service Binding Operator: %w", err)
   449  	}
   450  
   451  	// check if the remote resource is also present in the Devfile
   452  	for _, remoteK := range remoteK8sResources {
   453  		matchFound := false
   454  		isServiceBindingSecret := false
   455  		for _, devfileK := range devfileK8sResourcesUnstructured {
   456  			// only check against GroupKind because version might not always match
   457  			if remoteResourceIsPresentInDevfile := devfileK.GroupVersionKind().GroupKind() == remoteK.GroupVersionKind().GroupKind() &&
   458  				devfileK.GetName() == remoteK.GetName(); remoteResourceIsPresentInDevfile {
   459  				matchFound = true
   460  				break
   461  			}
   462  
   463  			// if the resource is a secret and the SBO is not installed, then check if it's related to a local ServiceBinding by checking the labels
   464  			if !isSBOSupported && remoteK.GroupVersionKind() == kclient.SecretGVK {
   465  				if remoteSecretHasLocalServiceBindingOwner := service.IsLinkSecret(remoteK.GetLabels()) &&
   466  					remoteK.GetLabels()[service.LinkLabel] == devfileK.GetName(); remoteSecretHasLocalServiceBindingOwner {
   467  					matchFound = true
   468  					isServiceBindingSecret = true
   469  					break
   470  				}
   471  			}
   472  		}
   473  
   474  		if !matchFound {
   475  			if isServiceBindingSecret {
   476  				serviceBindingSecretsToRemove = append(serviceBindingSecretsToRemove, remoteK)
   477  			} else {
   478  				objectsToRemove = append(objectsToRemove, remoteK)
   479  			}
   480  		}
   481  	}
   482  	return objectsToRemove, serviceBindingSecretsToRemove, nil
   483  }
   484  
   485  // deleteRemoteResources takes a list of remote resources to be deleted
   486  func (o DevClient) deleteRemoteResources(objectsToRemove []unstructured.Unstructured) error {
   487  	if len(objectsToRemove) == 0 {
   488  		return nil
   489  	}
   490  
   491  	var resources []string
   492  	for _, u := range objectsToRemove {
   493  		resources = append(resources, fmt.Sprintf("%s/%s", u.GetKind(), u.GetName()))
   494  	}
   495  
   496  	// Delete the resources present on the cluster but not in the Devfile
   497  	klog.V(3).Infof("Deleting %d resource(s) not present in the Devfile: %s", len(resources), strings.Join(resources, ", "))
   498  	g := new(errgroup.Group)
   499  	for _, objectToRemove := range objectsToRemove {
   500  		// Avoid re-use of the same `objectToRemove` value in each goroutine closure.
   501  		// See https://golang.org/doc/faq#closures_and_goroutines for more details.
   502  		objectToRemove := objectToRemove
   503  		g.Go(func() error {
   504  			gvr, err := o.kubernetesClient.GetGVRFromGVK(objectToRemove.GroupVersionKind())
   505  			if err != nil {
   506  				return fmt.Errorf("unable to get information about resource: %s/%s: %w", objectToRemove.GetKind(), objectToRemove.GetName(), err)
   507  			}
   508  
   509  			err = o.kubernetesClient.DeleteDynamicResource(objectToRemove.GetName(), gvr, true)
   510  			if err != nil {
   511  				if !(kerrors.IsNotFound(err) || kerrors.IsMethodNotSupported(err)) {
   512  					return fmt.Errorf("unable to delete resource: %s/%s: %w", objectToRemove.GetKind(), objectToRemove.GetName(), err)
   513  				}
   514  				klog.V(3).Infof("Failed to delete resource: %s/%s; resource not found or method not supported", objectToRemove.GetKind(), objectToRemove.GetName())
   515  			}
   516  
   517  			return nil
   518  		})
   519  	}
   520  
   521  	if err := g.Wait(); err != nil {
   522  		return err
   523  	}
   524  
   525  	return nil
   526  }
   527  
   528  // deleteServiceBindingSecrets takes a list of Service Binding secrets(unstructured) that should be deleted;
   529  // this is helpful when Service Binding Operator is not installed on the cluster
   530  func (o DevClient) deleteServiceBindingSecrets(serviceBindingSecretsToRemove []unstructured.Unstructured, deployment *appsv1.Deployment) error {
   531  	for _, secretToRemove := range serviceBindingSecretsToRemove {
   532  		spinner := log.Spinnerf("Deleting Kubernetes resource: %s/%s", secretToRemove.GetKind(), secretToRemove.GetName())
   533  		defer spinner.End(false)
   534  
   535  		err := service.UnbindWithLibrary(o.kubernetesClient, secretToRemove, deployment)
   536  		if err != nil {
   537  			return fmt.Errorf("failed to unbind secret %q from the application", secretToRemove.GetName())
   538  		}
   539  
   540  		// since the library currently doesn't delete the secret after unbinding
   541  		// delete the secret manually
   542  		err = o.kubernetesClient.DeleteSecret(secretToRemove.GetName(), o.kubernetesClient.GetCurrentNamespace())
   543  		if err != nil {
   544  			if !kerrors.IsNotFound(err) {
   545  				return fmt.Errorf("unable to delete Kubernetes resource: %s/%s: %s", secretToRemove.GetKind(), secretToRemove.GetName(), err.Error())
   546  			}
   547  			klog.V(4).Infof("Failed to delete Kubernetes resource: %s/%s; resource not found", secretToRemove.GetKind(), secretToRemove.GetName())
   548  		}
   549  		spinner.End(true)
   550  	}
   551  	return nil
   552  }
   553  
   554  // pushDevfileKubernetesComponents gets the Kubernetes components from the Devfile and push them to the cluster
   555  // adding the specified labels and ownerreference to them
   556  func (o *DevClient) pushDevfileKubernetesComponents(
   557  	ctx context.Context,
   558  	parameters common.PushParameters,
   559  	labels map[string]string,
   560  	mode string,
   561  	reference metav1.OwnerReference,
   562  ) ([]devfilev1.Component, error) {
   563  	var (
   564  		devfilePath = odocontext.GetDevfilePath(ctx)
   565  		path        = filepath.Dir(devfilePath)
   566  	)
   567  
   568  	// fetch the "kubernetes inlined components" to create them on cluster
   569  	// from odo standpoint, these components contain yaml manifest of ServiceBinding
   570  	k8sComponents, err := libdevfile.GetK8sAndOcComponentsToPush(parameters.Devfile, false)
   571  	if err != nil {
   572  		return nil, fmt.Errorf("error while trying to fetch service(s) from devfile: %w", err)
   573  	}
   574  
   575  	// validate if the GVRs represented by Kubernetes inlined components are supported by the underlying cluster
   576  	err = component.ValidateResourcesExist(o.kubernetesClient, parameters.Devfile, k8sComponents, path)
   577  	if err != nil {
   578  		return nil, err
   579  	}
   580  
   581  	// Set the annotations for the component type
   582  	annotations := make(map[string]string)
   583  	odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata()))
   584  
   585  	// create the Kubernetes objects from the manifest and delete the ones not in the devfile
   586  	err = service.PushKubernetesResources(o.kubernetesClient, parameters.Devfile, k8sComponents, labels, annotations, path, mode, reference)
   587  	if err != nil {
   588  		return nil, fmt.Errorf("failed to create Kubernetes resources associated with the component: %w", err)
   589  	}
   590  	return k8sComponents, nil
   591  }
   592  
   593  func (o *DevClient) updatePVCsOwnerReferences(ctx context.Context, ownerReference metav1.OwnerReference) error {
   594  	var (
   595  		componentName = odocontext.GetComponentName(ctx)
   596  	)
   597  
   598  	// list the latest state of the PVCs
   599  	pvcs, err := o.kubernetesClient.ListPVCs(fmt.Sprintf("%v=%v", "component", componentName))
   600  	if err != nil {
   601  		return err
   602  	}
   603  
   604  	// update the owner reference of the PVCs with the deployment
   605  	for i := range pvcs {
   606  		if pvcs[i].OwnerReferences != nil || pvcs[i].DeletionTimestamp != nil {
   607  			continue
   608  		}
   609  		err = o.kubernetesClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error {
   610  			return o.kubernetesClient.UpdateStorageOwnerReference(&pvcs[i], ownerRef)
   611  		})
   612  		if err != nil {
   613  			return err
   614  		}
   615  	}
   616  	return nil
   617  }
   618  
   619  // generateDeploymentObjectMeta generates a ObjectMeta object for the given deployment's name, labels and annotations
   620  // if no deployment exists, it creates a new deployment name
   621  func (o DevClient) generateDeploymentObjectMeta(ctx context.Context, deployment *appsv1.Deployment, labels map[string]string, annotations map[string]string) (metav1.ObjectMeta, error) {
   622  	var (
   623  		appName       = odocontext.GetApplication(ctx)
   624  		componentName = odocontext.GetComponentName(ctx)
   625  	)
   626  	if deployment != nil {
   627  		return generator.GetObjectMeta(deployment.Name, o.kubernetesClient.GetCurrentNamespace(), labels, annotations), nil
   628  	} else {
   629  		deploymentName, err := util.NamespaceKubernetesObject(componentName, appName)
   630  		if err != nil {
   631  			return metav1.ObjectMeta{}, err
   632  		}
   633  		return generator.GetObjectMeta(deploymentName, o.kubernetesClient.GetCurrentNamespace(), labels, annotations), nil
   634  	}
   635  }
   636  
   637  // buildVolumes:
   638  // - (side effect on cluster) creates the PVC for the project sources if Epehemeral preference is false
   639  // - (side effect on cluster) creates the PVCs for non-ephemeral volumes defined in the Devfile
   640  // - (side effect on input parameters) adds volumeMounts to containers and initContainers for the PVCs and Ephemeral volumes
   641  // - (side effect on input parameters) adds volumeMounts for automounted volumes
   642  // => Returns the list of Volumes to add to the PodTemplate
   643  func (o *DevClient) buildVolumes(ctx context.Context, parameters common.PushParameters, containers, initContainers []corev1.Container) ([]corev1.Volume, error) {
   644  	var (
   645  		appName       = odocontext.GetApplication(ctx)
   646  		componentName = odocontext.GetComponentName(ctx)
   647  	)
   648  
   649  	runtime := component.GetComponentRuntimeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata())
   650  
   651  	storageClient := storagepkg.NewClient(componentName, appName, storagepkg.ClientOptions{
   652  		Client:  o.kubernetesClient,
   653  		Runtime: runtime,
   654  	})
   655  
   656  	// Create the PVC for the project sources, if not ephemeral
   657  	err := storage.HandleOdoSourceStorage(o.kubernetesClient, storageClient, componentName, o.prefClient.GetEphemeralSourceVolume())
   658  	if err != nil {
   659  		return nil, err
   660  	}
   661  
   662  	// Create PVCs for non-ephemeral Volumes defined in the Devfile
   663  	// and returns the Ephemeral volumes defined in the Devfile
   664  	ephemerals, err := storagepkg.Push(storageClient, parameters.Devfile)
   665  	if err != nil {
   666  		return nil, err
   667  	}
   668  
   669  	// get all the PVCs from the cluster belonging to the component
   670  	// These PVCs have been created earlier with `storage.HandleOdoSourceStorage` and `storagepkg.Push`
   671  	pvcs, err := o.kubernetesClient.ListPVCs(fmt.Sprintf("%v=%v", "component", componentName))
   672  	if err != nil {
   673  		return nil, err
   674  	}
   675  
   676  	var allVolumes []corev1.Volume
   677  
   678  	// Get the name of the PVC for project sources + a map of (storageName => VolumeInfo)
   679  	// odoSourcePVCName will be empty when Ephemeral preference is true
   680  	odoSourcePVCName, volumeNameToVolInfo, err := storage.GetVolumeInfos(pvcs)
   681  	if err != nil {
   682  		return nil, err
   683  	}
   684  
   685  	// Add the volumes for the projects source and the Odo-specific directory
   686  	odoMandatoryVolumes := utils.GetOdoContainerVolumes(odoSourcePVCName)
   687  	allVolumes = append(allVolumes, odoMandatoryVolumes...)
   688  
   689  	// Add the volumeMounts for the project sources volume and the Odo-specific volume into the containers
   690  	utils.AddOdoProjectVolume(containers)
   691  	utils.AddOdoMandatoryVolume(containers)
   692  
   693  	// Get PVC volumes and Volume Mounts
   694  	pvcVolumes, err := storage.GetPersistentVolumesAndVolumeMounts(parameters.Devfile, containers, initContainers, volumeNameToVolInfo, parsercommon.DevfileOptions{})
   695  	if err != nil {
   696  		return nil, err
   697  	}
   698  	allVolumes = append(allVolumes, pvcVolumes...)
   699  
   700  	ephemeralVolumes, err := storage.GetEphemeralVolumesAndVolumeMounts(parameters.Devfile, containers, initContainers, ephemerals, parsercommon.DevfileOptions{})
   701  	if err != nil {
   702  		return nil, err
   703  	}
   704  	allVolumes = append(allVolumes, ephemeralVolumes...)
   705  
   706  	automountVolumes, err := storage.GetAutomountVolumes(o.configAutomountClient, containers, initContainers)
   707  	if err != nil {
   708  		return nil, err
   709  	}
   710  	allVolumes = append(allVolumes, automountVolumes...)
   711  
   712  	return allVolumes, nil
   713  }
   714  
   715  func (o *DevClient) createOrUpdateServiceForComponent(ctx context.Context, svc *corev1.Service, ownerReference metav1.OwnerReference) error {
   716  	var (
   717  		appName       = odocontext.GetApplication(ctx)
   718  		componentName = odocontext.GetComponentName(ctx)
   719  	)
   720  	oldSvc, err := o.kubernetesClient.GetOneService(componentName, appName, true)
   721  	originOwnerReferences := svc.OwnerReferences
   722  	if err != nil {
   723  		// no old service was found, create a new one
   724  		if len(svc.Spec.Ports) > 0 {
   725  			err = o.kubernetesClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error {
   726  				svc.OwnerReferences = append(originOwnerReferences, ownerReference)
   727  				_, err = o.kubernetesClient.CreateService(*svc)
   728  				return err
   729  			})
   730  			if err != nil {
   731  				return err
   732  			}
   733  			klog.V(2).Infof("Successfully created Service for component %s", componentName)
   734  		}
   735  		return nil
   736  	}
   737  	if len(svc.Spec.Ports) > 0 {
   738  		svc.Spec.ClusterIP = oldSvc.Spec.ClusterIP
   739  		svc.ResourceVersion = oldSvc.GetResourceVersion()
   740  		err = o.kubernetesClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error {
   741  			svc.OwnerReferences = append(originOwnerReferences, ownerRef)
   742  			_, err = o.kubernetesClient.UpdateService(*svc)
   743  			return err
   744  		})
   745  		if err != nil {
   746  			return err
   747  		}
   748  		klog.V(2).Infof("Successfully update Service for component %s", componentName)
   749  		return nil
   750  	}
   751  	// delete the old existing service if the component currently doesn't expose any ports
   752  	return o.kubernetesClient.DeleteService(oldSvc.Name)
   753  }
   754  

View as plain text