...

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

Documentation: github.com/redhat-developer/odo/pkg/component/delete

     1  package delete
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path/filepath"
     7  
     8  	"github.com/devfile/library/v2/pkg/devfile/parser"
     9  	v1 "k8s.io/api/apps/v1"
    10  	corev1 "k8s.io/api/core/v1"
    11  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    12  	"k8s.io/apimachinery/pkg/api/meta"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    15  	"k8s.io/klog"
    16  
    17  	"github.com/redhat-developer/odo/pkg/component"
    18  	"github.com/redhat-developer/odo/pkg/configAutomount"
    19  	"github.com/redhat-developer/odo/pkg/exec"
    20  	"github.com/redhat-developer/odo/pkg/kclient"
    21  	odolabels "github.com/redhat-developer/odo/pkg/labels"
    22  	"github.com/redhat-developer/odo/pkg/libdevfile"
    23  	"github.com/redhat-developer/odo/pkg/log"
    24  	clierrors "github.com/redhat-developer/odo/pkg/odo/cli/errors"
    25  	odocontext "github.com/redhat-developer/odo/pkg/odo/context"
    26  	"github.com/redhat-developer/odo/pkg/platform"
    27  	"github.com/redhat-developer/odo/pkg/podman"
    28  	"github.com/redhat-developer/odo/pkg/util"
    29  )
    30  
    31  type DeleteComponentClient struct {
    32  	kubeClient            kclient.ClientInterface
    33  	podmanClient          podman.Client
    34  	execClient            exec.Client
    35  	configAutomountClient configAutomount.Client
    36  }
    37  
    38  var _ Client = (*DeleteComponentClient)(nil)
    39  
    40  func NewDeleteComponentClient(
    41  	kubeClient kclient.ClientInterface,
    42  	podmanClient podman.Client,
    43  	execClient exec.Client,
    44  	configAutomountClient configAutomount.Client,
    45  ) *DeleteComponentClient {
    46  	return &DeleteComponentClient{
    47  		kubeClient:            kubeClient,
    48  		podmanClient:          podmanClient,
    49  		execClient:            execClient,
    50  		configAutomountClient: configAutomountClient,
    51  	}
    52  }
    53  
    54  // ListClusterResourcesToDelete lists Kubernetes resources from cluster in namespace for a given odo component
    55  // It only returns resources not owned by another resource of the component, letting the garbage collector do its job
    56  func (do *DeleteComponentClient) ListClusterResourcesToDelete(
    57  	ctx context.Context,
    58  	componentName string,
    59  	namespace string,
    60  	mode string,
    61  ) ([]unstructured.Unstructured, error) {
    62  	var result []unstructured.Unstructured
    63  	selector := odolabels.GetSelector(componentName, odocontext.GetApplication(ctx), mode, false)
    64  	list, err := do.kubeClient.GetAllResourcesFromSelector(selector, namespace)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	for _, resource := range list {
    69  		// If the resource is Terminating, there is no sense in displaying it.
    70  		if resource.GetDeletionTimestamp() != nil {
    71  			continue
    72  		}
    73  		referenced := false
    74  		for _, ownerRef := range resource.GetOwnerReferences() {
    75  			if references(list, ownerRef) {
    76  				referenced = true
    77  				break
    78  			}
    79  		}
    80  		if !referenced {
    81  			result = append(result, resource)
    82  		}
    83  	}
    84  
    85  	return result, nil
    86  }
    87  
    88  func (do *DeleteComponentClient) DeleteResources(resources []unstructured.Unstructured, wait bool) []unstructured.Unstructured {
    89  	var failed []unstructured.Unstructured
    90  	for _, resource := range resources {
    91  		gvr, err := do.kubeClient.GetRestMappingFromUnstructured(resource)
    92  		if err != nil {
    93  			failed = append(failed, resource)
    94  			continue
    95  		}
    96  		err = do.kubeClient.DeleteDynamicResource(resource.GetName(), gvr.Resource, wait)
    97  		if err != nil && !kerrors.IsNotFound(err) {
    98  			klog.V(3).Infof("failed to delete resource %q (%s.%s.%s): %v", resource.GetName(), gvr.Resource.Group, gvr.Resource.Version, gvr.Resource.Resource, err)
    99  			failed = append(failed, resource)
   100  		}
   101  	}
   102  	return failed
   103  }
   104  
   105  // references returns true if ownerRef references a resource in the list
   106  func references(list []unstructured.Unstructured, ownerRef metav1.OwnerReference) bool {
   107  	for _, resource := range list {
   108  		if ownerRef.APIVersion == resource.GetAPIVersion() && ownerRef.Kind == resource.GetKind() && ownerRef.Name == resource.GetName() {
   109  			return true
   110  		}
   111  	}
   112  	return false
   113  }
   114  
   115  // ListResourcesToDeleteFromDevfile parses all the devfile components and returns a list of resources that are present on the cluster and can be deleted
   116  // Returns a Warning if an error happens communicating with the cluster
   117  func (do DeleteComponentClient) ListClusterResourcesToDeleteFromDevfile(devfileObj parser.DevfileObj, appName string, componentName string, mode string) (isInnerLoopDeployed bool, resources []unstructured.Unstructured, err error) {
   118  	var deployment *v1.Deployment
   119  	if mode == odolabels.ComponentDevMode || mode == odolabels.ComponentAnyMode {
   120  		// Inner Loop
   121  		var deploymentName string
   122  		deploymentName, err = util.NamespaceKubernetesObject(componentName, appName)
   123  		if err != nil {
   124  			return isInnerLoopDeployed, resources, fmt.Errorf("failed to get the resource %q name for component %q; cause: %w", kclient.DeploymentKind, componentName, err)
   125  		}
   126  
   127  		deployment, err = do.kubeClient.GetDeploymentByName(deploymentName)
   128  		if err != nil && !kerrors.IsNotFound(err) {
   129  			// Kubernetes cluster access fails, return with a warning only
   130  			err = clierrors.NewWarning(fmt.Sprintf("failed to get deployment %q", deploymentName), err)
   131  			return isInnerLoopDeployed, resources, err
   132  		}
   133  
   134  		// if the deployment is found on the cluster,
   135  		// then convert it to unstructured.Unstructured object so that it can be appended to resources;
   136  		// else continue to outer loop
   137  		if deployment.Name != "" {
   138  			isInnerLoopDeployed = true
   139  			var unstructuredDeploy unstructured.Unstructured
   140  			unstructuredDeploy, err = kclient.ConvertK8sResourceToUnstructured(deployment)
   141  			if err != nil {
   142  				return isInnerLoopDeployed, resources, fmt.Errorf("failed to parse the resource %q: %q; cause: %w", kclient.DeploymentKind, deploymentName, err)
   143  			}
   144  			resources = append(resources, unstructuredDeploy)
   145  		}
   146  	}
   147  
   148  	// Parse the devfile for K8s resources; these may belong to either innerloop or outerloop
   149  	localK8sResources, err := libdevfile.ListKubernetesComponents(devfileObj, filepath.Dir(devfileObj.Ctx.GetAbsPath()))
   150  	if err != nil {
   151  		return isInnerLoopDeployed, resources, fmt.Errorf("failed to gather resources for deletion: %w", err)
   152  	}
   153  	localOCResources, err := libdevfile.ListOpenShiftComponents(devfileObj, filepath.Dir(devfileObj.Ctx.GetAbsPath()))
   154  	if err != nil {
   155  		return isInnerLoopDeployed, resources, fmt.Errorf("failed to gather resources for deletion: %w", err)
   156  	}
   157  
   158  	localAllResources := []unstructured.Unstructured{}
   159  	localAllResources = append(localAllResources, localOCResources...)
   160  	localAllResources = append(localAllResources, localK8sResources...)
   161  
   162  	for _, lr := range localAllResources {
   163  		var gvr *meta.RESTMapping
   164  		gvr, err = do.kubeClient.GetRestMappingFromUnstructured(lr)
   165  		if err != nil {
   166  			continue
   167  		}
   168  		// Try to fetch the resource from the cluster; if it exists, append it to the resources list
   169  		var cr *unstructured.Unstructured
   170  		cr, err = do.kubeClient.GetDynamicResource(gvr.Resource, lr.GetName())
   171  		// If a specific mode is asked for, then make sure it matches with the cr's mode.
   172  		if err != nil || (mode != odolabels.ComponentAnyMode && odolabels.GetMode(cr.GetLabels()) != mode) {
   173  			if cr != nil {
   174  				klog.V(4).Infof("Ignoring resource: %s/%s; its mode(%s) does not match with the given mode(%s)", gvr.Resource.Resource, lr.GetName(), odolabels.GetMode(cr.GetLabels()), mode)
   175  			} else {
   176  				klog.V(4).Infof("Ignoring resource: %s/%s; it does not exist on the cluster", gvr.Resource.Resource, lr.GetName())
   177  			}
   178  			continue
   179  		}
   180  		resources = append(resources, *cr)
   181  	}
   182  
   183  	return isInnerLoopDeployed, resources, nil
   184  }
   185  
   186  // ExecutePreStopEvents executes preStop events if any, as a precondition to deleting a devfile component deployment
   187  func (do *DeleteComponentClient) ExecutePreStopEvents(ctx context.Context, devfileObj parser.DevfileObj, appName string, componentName string) error {
   188  	if !libdevfile.HasPreStopEvents(devfileObj) {
   189  		return nil
   190  	}
   191  
   192  	klog.V(4).Infof("Gathering information for component: %q", componentName)
   193  
   194  	klog.V(3).Infof("Checking component status for %q", componentName)
   195  	selector := odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode, false)
   196  	pod, err := do.kubeClient.GetRunningPodFromSelector(selector)
   197  	if err != nil {
   198  		klog.V(1).Info("Component not found on the cluster.")
   199  
   200  		if kerrors.IsForbidden(err) {
   201  			klog.V(3).Infof("Resource for %q forbidden", componentName)
   202  			log.Warningf("You are forbidden from accessing the resource. Please check if you the right permissions and try again.")
   203  			return nil
   204  		}
   205  
   206  		if e, ok := err.(*platform.PodNotFoundError); ok {
   207  			klog.V(3).Infof("Resource for %q not found; cause: %v", componentName, e)
   208  			log.Warningf("Resources not found on the cluster. Run `odo delete component -v <DEBUG_LEVEL_0-9>` to know more.")
   209  			return nil
   210  		}
   211  
   212  		return fmt.Errorf("unable to determine if component %s exists; cause: %v", componentName, err.Error())
   213  	}
   214  
   215  	klog.V(4).Infof("Executing %q event commands for component %q", libdevfile.PreStop, componentName)
   216  	// ignore the failures if any; delete should not fail because preStop events failed to execute
   217  	handler := component.NewRunHandler(
   218  		ctx,
   219  		do.kubeClient,
   220  		do.execClient,
   221  		do.configAutomountClient,
   222  		// TODO(feloy) set these values when we want to support Apply Image commands for PreStop events
   223  		nil, nil,
   224  
   225  		component.HandlerOptions{
   226  			PodName:           pod.Name,
   227  			ContainersRunning: component.GetContainersNames(pod),
   228  			Msg:               "Executing pre-stop command in container",
   229  		},
   230  	)
   231  	err = libdevfile.ExecPreStopEvents(ctx, devfileObj, handler)
   232  	if err != nil {
   233  		log.Warningf("Failed to execute %q event commands for component %q, cause: %v", libdevfile.PreStop, componentName, err.Error())
   234  	}
   235  
   236  	return nil
   237  }
   238  
   239  func (do *DeleteComponentClient) ListPodmanResourcesToDelete(appName string, componentName string, mode string) (isInnerLoopDeployed bool, pods []*corev1.Pod, err error) {
   240  	if mode == odolabels.ComponentDeployMode {
   241  		return false, nil, nil
   242  	}
   243  
   244  	// Inner Loop
   245  	var podName string
   246  	podName, err = util.NamespaceKubernetesObject(componentName, appName)
   247  	if err != nil {
   248  		return false, nil, fmt.Errorf("failed to get the resource %q name for component %q; cause: %w", kclient.DeploymentKind, componentName, err)
   249  	}
   250  
   251  	allPods, err := do.podmanClient.PodLs()
   252  	if err != nil {
   253  		err = clierrors.NewWarning("failed to get pods on podman", err)
   254  		return false, nil, err
   255  	}
   256  
   257  	if _, isInnerLoopDeployed = allPods[podName]; isInnerLoopDeployed {
   258  		podDef, err := do.podmanClient.KubeGenerate(podName)
   259  		if err != nil {
   260  			return false, nil, err
   261  		}
   262  		pods = append(pods, podDef)
   263  	}
   264  	return isInnerLoopDeployed, pods, nil
   265  }
   266  

View as plain text