github.com/oam-dev/kubevela@v1.9.11/pkg/velaql/providers/query/collector.go (about)

     1  /*
     2   Copyright 2021. The KubeVela Authors.
     3  
     4   Licensed under the Apache License, Version 2.0 (the "License");
     5   you may not use this file except in compliance with the License.
     6   You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10   Unless required by applicable law or agreed to in writing, software
    11   distributed under the License is distributed on an "AS IS" BASIS,
    12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   See the License for the specific language governing permissions and
    14   limitations under the License.
    15  */
    16  
    17  package query
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/hashicorp/go-version"
    23  	"github.com/pkg/errors"
    24  	corev1 "k8s.io/api/core/v1"
    25  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/fields"
    28  	"k8s.io/klog/v2"
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  
    31  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    32  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    33  	"github.com/oam-dev/kubevela/pkg/multicluster"
    34  	"github.com/oam-dev/kubevela/pkg/oam"
    35  	"github.com/oam-dev/kubevela/pkg/resourcetracker"
    36  	"github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
    37  )
    38  
    39  // AppCollector collect resource created by application
    40  type AppCollector struct {
    41  	k8sClient client.Client
    42  	opt       Option
    43  }
    44  
    45  // NewAppCollector create a app collector
    46  func NewAppCollector(cli client.Client, opt Option) *AppCollector {
    47  	return &AppCollector{
    48  		k8sClient: cli,
    49  		opt:       opt,
    50  	}
    51  }
    52  
    53  const velaVersionNumberToUpgradeVelaQL = "v1.2.0-rc.1"
    54  
    55  // CollectResourceFromApp collect resources created by application
    56  func (c *AppCollector) CollectResourceFromApp(ctx context.Context) ([]Resource, error) {
    57  	app := new(v1beta1.Application)
    58  	appKey := client.ObjectKey{Name: c.opt.Name, Namespace: c.opt.Namespace}
    59  	if err := c.k8sClient.Get(ctx, appKey, app); err != nil {
    60  		return nil, err
    61  	}
    62  	var currentVersionNumber string
    63  	if annotations := app.GetAnnotations(); annotations != nil && annotations[oam.AnnotationKubeVelaVersion] != "" {
    64  		currentVersionNumber = annotations[oam.AnnotationKubeVelaVersion]
    65  	}
    66  	velaVersionToUpgradeVelaQL, _ := version.NewVersion(velaVersionNumberToUpgradeVelaQL)
    67  	currentVersion, err := version.NewVersion(currentVersionNumber)
    68  	if err != nil {
    69  		resources, err := c.FindResourceFromResourceTrackerSpec(ctx, app)
    70  		if err != nil {
    71  			return c.FindResourceFromAppliedResourcesField(ctx, app)
    72  		}
    73  		return resources, nil
    74  	}
    75  
    76  	if velaVersionToUpgradeVelaQL.GreaterThan(currentVersion) {
    77  		return c.FindResourceFromAppliedResourcesField(ctx, app)
    78  	}
    79  	return c.FindResourceFromResourceTrackerSpec(ctx, app)
    80  }
    81  
    82  // ListApplicationResources list application applied resources from tracker
    83  func (c *AppCollector) ListApplicationResources(ctx context.Context, app *v1beta1.Application) ([]*types.AppliedResource, error) {
    84  	rootRT, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, c.k8sClient, app)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	var managedResources []*types.AppliedResource
    89  	existResources := make(map[common.ClusterObjectReference]bool, len(app.Spec.Components))
    90  	if c.opt.Filter.QueryNewest {
    91  		historyRTs = nil
    92  	}
    93  	for _, rt := range append(historyRTs, rootRT, currentRT) {
    94  		if rt != nil {
    95  			for _, managedResource := range rt.Spec.ManagedResources {
    96  				if isResourceInTargetCluster(c.opt.Filter, managedResource.ClusterObjectReference) &&
    97  					isResourceInTargetComponent(c.opt.Filter, managedResource.Component) &&
    98  					(c.opt.WithTree || isResourceMatchKindAndVersion(c.opt.Filter, managedResource.Kind, managedResource.APIVersion)) {
    99  					if c.opt.WithTree {
   100  						// If we want to query the tree, we only need to query once for the same resource.
   101  						if _, exist := existResources[managedResource.ClusterObjectReference]; exist {
   102  							continue
   103  						}
   104  						existResources[managedResource.ClusterObjectReference] = true
   105  					}
   106  					managedResources = append(managedResources, &types.AppliedResource{
   107  						Cluster: func() string {
   108  							if managedResource.Cluster != "" {
   109  								return managedResource.Cluster
   110  							}
   111  							return "local"
   112  						}(),
   113  						Kind:            managedResource.Kind,
   114  						Component:       managedResource.Component,
   115  						Trait:           managedResource.Trait,
   116  						Name:            managedResource.Name,
   117  						Namespace:       managedResource.Namespace,
   118  						APIVersion:      managedResource.APIVersion,
   119  						ResourceVersion: managedResource.ResourceVersion,
   120  						UID:             managedResource.UID,
   121  						PublishVersion:  oam.GetPublishVersion(rt),
   122  						DeployVersion: func() string {
   123  							obj, _ := managedResource.ToUnstructuredWithData()
   124  							if obj != nil {
   125  								return oam.GetDeployVersion(obj)
   126  							}
   127  							return ""
   128  						}(),
   129  						Revision: rt.GetLabels()[oam.LabelAppRevision],
   130  						Latest:   currentRT != nil && rt.Name == currentRT.Name,
   131  					})
   132  				}
   133  			}
   134  		}
   135  	}
   136  
   137  	if !c.opt.WithTree {
   138  		return managedResources, nil
   139  	}
   140  
   141  	// merge user defined customize rule before every request.
   142  	err = mergeCustomRules(ctx, c.k8sClient)
   143  	if err != nil {
   144  		return managedResources, err
   145  	}
   146  
   147  	filter := func(node types.ResourceTreeNode) bool {
   148  		return isResourceMatchKindAndVersion(c.opt.Filter, node.Kind, node.APIVersion)
   149  	}
   150  	var matchedResources []*types.AppliedResource
   151  	// error from leaf nodes won't block the results
   152  	for i := range managedResources {
   153  		resource := managedResources[i]
   154  		root := types.ResourceTreeNode{
   155  			Cluster:    resource.Cluster,
   156  			APIVersion: resource.APIVersion,
   157  			Kind:       resource.Kind,
   158  			Namespace:  resource.Namespace,
   159  			Name:       resource.Name,
   160  			UID:        resource.UID,
   161  		}
   162  		root.LeafNodes, err = iterateListSubResources(ctx, resource.Cluster, c.k8sClient, root, 1, filter)
   163  		if err != nil {
   164  			// if the resource has been deleted, continue access next appliedResource don't break the whole request
   165  			if kerrors.IsNotFound(err) {
   166  				continue
   167  			}
   168  			klog.Errorf("query leaf node resource apiVersion=%s kind=%s namespace=%s name=%s failure %s, skip this resource", root.APIVersion, root.Kind, root.Namespace, root.Name, err.Error())
   169  			continue
   170  		}
   171  		if !filter(root) && len(root.LeafNodes) == 0 {
   172  			continue
   173  		}
   174  		rootObject, err := fetchObjectWithResourceTreeNode(ctx, resource.Cluster, c.k8sClient, root)
   175  		if err != nil {
   176  			// if the resource has been deleted, continue access next appliedResource don't break the whole request
   177  			if kerrors.IsNotFound(err) {
   178  				continue
   179  			}
   180  			klog.Errorf("fetch object for resource apiVersion=%s kind=%s namespace=%s name=%s failure %s, skip this resource", root.APIVersion, root.Kind, root.Namespace, root.Name, err.Error())
   181  			continue
   182  		}
   183  		rootStatus, err := CheckResourceStatus(*rootObject)
   184  		if err != nil {
   185  			klog.Errorf("check status for resource apiVersion=%s kind=%s namespace=%s name=%s failure %s, skip this resource", root.APIVersion, root.Kind, root.Namespace, root.Name, err.Error())
   186  			continue
   187  		}
   188  		root.HealthStatus = *rootStatus
   189  		addInfo, err := additionalInfo(*rootObject)
   190  		if err != nil {
   191  			klog.Errorf("check additionalInfo for resource apiVersion=%s kind=%s namespace=%s name=%s failure %s, skip this resource", root.APIVersion, root.Kind, root.Namespace, root.Name, err.Error())
   192  			continue
   193  		}
   194  		root.AdditionalInfo = addInfo
   195  		root.CreationTimestamp = rootObject.GetCreationTimestamp().Time
   196  		if !rootObject.GetDeletionTimestamp().IsZero() {
   197  			root.DeletionTimestamp = rootObject.GetDeletionTimestamp().Time
   198  		}
   199  		root.Object = *rootObject
   200  		resource.ResourceTree = &root
   201  		matchedResources = append(matchedResources, resource)
   202  	}
   203  	return matchedResources, nil
   204  }
   205  
   206  // FindResourceFromResourceTrackerSpec find resources from ResourceTracker spec
   207  func (c *AppCollector) FindResourceFromResourceTrackerSpec(ctx context.Context, app *v1beta1.Application) ([]Resource, error) {
   208  	rootRT, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, c.k8sClient, app)
   209  	if err != nil {
   210  		klog.Errorf("query the resourcetrackers failure %s", err.Error())
   211  		return nil, err
   212  	}
   213  	var resources = []Resource{}
   214  	existResources := make(map[common.ClusterObjectReference]bool, len(app.Spec.Components))
   215  	for _, rt := range append([]*v1beta1.ResourceTracker{rootRT, currentRT}, historyRTs...) {
   216  		if rt != nil {
   217  			for _, managedResource := range rt.Spec.ManagedResources {
   218  				if isResourceInTargetCluster(c.opt.Filter, managedResource.ClusterObjectReference) &&
   219  					isResourceInTargetComponent(c.opt.Filter, managedResource.Component) &&
   220  					isResourceMatchKindAndVersion(c.opt.Filter, managedResource.Kind, managedResource.APIVersion) {
   221  					if _, exist := existResources[managedResource.ClusterObjectReference]; exist {
   222  						continue
   223  					}
   224  					existResources[managedResource.ClusterObjectReference] = true
   225  					obj, err := managedResource.ToUnstructuredWithData()
   226  					if err != nil || c.opt.WithStatus {
   227  						// For the application with apply once policy, there is no data in RT.
   228  						// IF the WithStatus is true, get the object from cluster
   229  						_, obj, err = getObjectCreatedByComponent(ctx, c.k8sClient, managedResource.ObjectReference, managedResource.Cluster)
   230  						if err != nil {
   231  							klog.Errorf("get obj from the cluster failure %s", err.Error())
   232  							continue
   233  						}
   234  					}
   235  					clusterName := managedResource.Cluster
   236  					if clusterName == "" {
   237  						clusterName = multicluster.ClusterLocalName
   238  					}
   239  					resources = append(resources, Resource{
   240  						Cluster:   clusterName,
   241  						Revision:  oam.GetPublishVersion(rt),
   242  						Component: managedResource.Component,
   243  						Object:    obj,
   244  					})
   245  				}
   246  			}
   247  		}
   248  	}
   249  	return resources, nil
   250  }
   251  
   252  // FindResourceFromAppliedResourcesField find resources from AppliedResources field
   253  func (c *AppCollector) FindResourceFromAppliedResourcesField(ctx context.Context, app *v1beta1.Application) ([]Resource, error) {
   254  	resources := make([]Resource, 0, len(app.Spec.Components))
   255  	for _, res := range app.Status.AppliedResources {
   256  		if !isResourceInTargetCluster(c.opt.Filter, res) {
   257  			continue
   258  		}
   259  		if !isResourceMatchKindAndVersion(c.opt.Filter, res.APIVersion, res.Kind) {
   260  			continue
   261  		}
   262  		compName, obj, err := getObjectCreatedByComponent(ctx, c.k8sClient, res.ObjectReference, res.Cluster)
   263  		if err != nil {
   264  			return nil, err
   265  		}
   266  		if len(compName) != 0 && isResourceInTargetComponent(c.opt.Filter, compName) {
   267  			resources = append(resources, Resource{
   268  				Component: compName,
   269  				Revision:  obj.GetLabels()[oam.LabelAppRevision],
   270  				Cluster:   res.Cluster,
   271  				Object:    obj,
   272  			})
   273  		}
   274  	}
   275  	if len(resources) == 0 {
   276  		return nil, errors.Errorf("fail to find resources created by application: %v", c.opt.Name)
   277  	}
   278  	return resources, nil
   279  }
   280  
   281  // getObjectCreatedByComponent get k8s obj created by components
   282  func getObjectCreatedByComponent(ctx context.Context, cli client.Client, objRef corev1.ObjectReference, cluster string) (string, *unstructured.Unstructured, error) {
   283  	ctx = multicluster.ContextWithClusterName(ctx, cluster)
   284  	obj := new(unstructured.Unstructured)
   285  	obj.SetGroupVersionKind(objRef.GroupVersionKind())
   286  	obj.SetNamespace(objRef.Namespace)
   287  	obj.SetName(objRef.Name)
   288  	if err := cli.Get(ctx, client.ObjectKeyFromObject(obj), obj); err != nil {
   289  		if kerrors.IsNotFound(err) {
   290  			return "", nil, nil
   291  		}
   292  		return "", nil, err
   293  	}
   294  	componentName := obj.GetLabels()[oam.LabelAppComponent]
   295  	return componentName, obj, nil
   296  }
   297  
   298  func getEventFieldSelector(obj *unstructured.Unstructured) fields.Selector {
   299  	field := fields.Set{}
   300  	field["involvedObject.name"] = obj.GetName()
   301  	field["involvedObject.namespace"] = obj.GetNamespace()
   302  	field["involvedObject.kind"] = obj.GetObjectKind().GroupVersionKind().Kind
   303  	field["involvedObject.uid"] = string(obj.GetUID())
   304  	return field.AsSelector()
   305  }
   306  
   307  func isResourceInTargetCluster(opt FilterOption, resource common.ClusterObjectReference) bool {
   308  	if opt.Cluster == "" && opt.ClusterNamespace == "" {
   309  		return true
   310  	}
   311  	if (opt.Cluster == resource.Cluster || (opt.Cluster == "local" && resource.Cluster == "")) &&
   312  		(opt.ClusterNamespace == resource.ObjectReference.Namespace || opt.ClusterNamespace == "") {
   313  		return true
   314  	}
   315  
   316  	return false
   317  }
   318  
   319  func isResourceInTargetComponent(opt FilterOption, componentName string) bool {
   320  	if len(opt.Components) == 0 {
   321  		return true
   322  	}
   323  	for _, component := range opt.Components {
   324  		if component == componentName {
   325  			return true
   326  		}
   327  	}
   328  	return false
   329  }
   330  
   331  func isResourceMatchKindAndVersion(opt FilterOption, kind, version string) bool {
   332  	if opt.APIVersion != "" && opt.APIVersion != version {
   333  		return false
   334  	}
   335  	if opt.Kind != "" && opt.Kind != kind {
   336  		return false
   337  	}
   338  	return true
   339  }