github.com/oam-dev/kubevela@v1.9.11/pkg/velaql/providers/query/tree.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  	"encoding/json"
    22  	"fmt"
    23  	"os"
    24  	"sort"
    25  	"strconv"
    26  	"time"
    27  
    28  	appsv1 "k8s.io/api/apps/v1"
    29  	v12 "k8s.io/api/core/v1"
    30  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/api/meta"
    32  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	"k8s.io/apimachinery/pkg/labels"
    35  	"k8s.io/apimachinery/pkg/runtime"
    36  	types2 "k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/apimachinery/pkg/util/duration"
    38  	"k8s.io/klog/v2"
    39  	"k8s.io/kubectl/pkg/util/podutils"
    40  
    41  	"sigs.k8s.io/controller-runtime/pkg/client"
    42  	"sigs.k8s.io/yaml"
    43  
    44  	velatypes "github.com/oam-dev/kubevela/apis/types"
    45  	"github.com/oam-dev/kubevela/pkg/multicluster"
    46  	"github.com/oam-dev/kubevela/pkg/oam"
    47  	"github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
    48  
    49  	helmreleaseapi "github.com/fluxcd/helm-controller/api/v2beta1"
    50  	helmrepoapi "github.com/fluxcd/source-controller/api/v1beta2"
    51  )
    52  
    53  const (
    54  	// DefaultMaxDepth is the default max depth for query iterator
    55  	// check maxDepth function to get the customized val for max depth
    56  	DefaultMaxDepth = 5
    57  )
    58  
    59  // relationshipKey is the configmap key of relationShip rule
    60  var relationshipKey = "rules"
    61  
    62  // RuleList the rule list
    63  type RuleList []ChildrenResourcesRule
    64  
    65  // GetRule get the rule by the resource type
    66  func (rl *RuleList) GetRule(grt GroupResourceType) (*ChildrenResourcesRule, bool) {
    67  	for i, r := range *rl {
    68  		if r.GroupResourceType == grt {
    69  			return &(*rl)[i], true
    70  		}
    71  	}
    72  	return nil, false
    73  }
    74  
    75  // globalRule define the whole relationShip rule
    76  var globalRule RuleList
    77  
    78  func init() {
    79  	globalRule = append(globalRule,
    80  		ChildrenResourcesRule{
    81  			GroupResourceType: GroupResourceType{Group: "apps", Kind: "Deployment"},
    82  			SubResources: buildSubResources([]*SubResourceSelector{
    83  				{
    84  					ResourceType: ResourceType{APIVersion: "apps/v1", Kind: "ReplicaSet"},
    85  					listOptions:  defaultWorkloadLabelListOption,
    86  				},
    87  			}),
    88  		},
    89  		ChildrenResourcesRule{
    90  			SubResources: buildSubResources([]*SubResourceSelector{
    91  				{
    92  					ResourceType: ResourceType{APIVersion: "v1", Kind: "Pod"},
    93  					listOptions:  defaultWorkloadLabelListOption,
    94  				},
    95  			}),
    96  			GroupResourceType: GroupResourceType{Group: "apps", Kind: "ReplicaSet"},
    97  		},
    98  		ChildrenResourcesRule{
    99  			SubResources: buildSubResources([]*SubResourceSelector{
   100  				{
   101  					ResourceType: ResourceType{APIVersion: "v1", Kind: "Pod"},
   102  					listOptions:  defaultWorkloadLabelListOption,
   103  				},
   104  			}),
   105  			GroupResourceType: GroupResourceType{Group: "apps", Kind: "StatefulSet"},
   106  		},
   107  		ChildrenResourcesRule{
   108  			SubResources: buildSubResources([]*SubResourceSelector{
   109  				{
   110  					ResourceType: ResourceType{APIVersion: "v1", Kind: "Pod"},
   111  					listOptions:  defaultWorkloadLabelListOption,
   112  				},
   113  			}),
   114  			GroupResourceType: GroupResourceType{Group: "apps", Kind: "DaemonSet"},
   115  		},
   116  		ChildrenResourcesRule{
   117  			GroupResourceType: GroupResourceType{Group: "batch", Kind: "Job"},
   118  			SubResources: buildSubResources([]*SubResourceSelector{
   119  				{
   120  					ResourceType: ResourceType{APIVersion: "v1", Kind: "Pod"},
   121  					listOptions:  defaultWorkloadLabelListOption,
   122  				},
   123  			}),
   124  		},
   125  		ChildrenResourcesRule{
   126  			GroupResourceType: GroupResourceType{Group: "", Kind: "Service"},
   127  			SubResources: buildSubResources([]*SubResourceSelector{
   128  				{
   129  					ResourceType: ResourceType{APIVersion: "discovery.k8s.io/v1beta1", Kind: "EndpointSlice"},
   130  				},
   131  				{
   132  					ResourceType: ResourceType{APIVersion: "discovery.k8s.io/v1", Kind: "EndpointSlice"},
   133  				},
   134  				{
   135  					ResourceType: ResourceType{APIVersion: "v1", Kind: "Endpoints"},
   136  					listOptions:  service2EndpointListOption,
   137  				},
   138  			}),
   139  		},
   140  		ChildrenResourcesRule{
   141  			GroupResourceType: GroupResourceType{Group: "helm.toolkit.fluxcd.io", Kind: "HelmRelease"},
   142  			SubResources: buildSubResources([]*SubResourceSelector{
   143  				{
   144  					ResourceType: ResourceType{APIVersion: "apps/v1", Kind: "Deployment"},
   145  				},
   146  				{
   147  					ResourceType: ResourceType{APIVersion: "apps/v1", Kind: "StatefulSet"},
   148  				},
   149  				{
   150  					ResourceType: ResourceType{APIVersion: "v1", Kind: "ConfigMap"},
   151  				},
   152  				{
   153  					ResourceType: ResourceType{APIVersion: "v1", Kind: "Secret"},
   154  				},
   155  				{
   156  					ResourceType: ResourceType{APIVersion: "v1", Kind: "Service"},
   157  				},
   158  				{
   159  					ResourceType: ResourceType{APIVersion: "v1", Kind: "PersistentVolumeClaim"},
   160  				},
   161  				{
   162  					ResourceType: ResourceType{APIVersion: "networking.k8s.io/v1", Kind: "Ingress"},
   163  				},
   164  				{
   165  					ResourceType: ResourceType{APIVersion: "gateway.networking.k8s.io/v1beta1", Kind: "HTTPRoute"},
   166  				},
   167  				{
   168  					ResourceType: ResourceType{APIVersion: "gateway.networking.k8s.io/v1beta1", Kind: "Gateway"},
   169  				},
   170  				{
   171  					ResourceType: ResourceType{APIVersion: "v1", Kind: "ServiceAccount"},
   172  				},
   173  				{
   174  					ResourceType: ResourceType{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
   175  				},
   176  				{
   177  					ResourceType: ResourceType{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "RoleBinding"},
   178  				},
   179  			}),
   180  			DefaultGenListOptionFunc:      helmRelease2AnyListOption,
   181  			DisableFilterByOwnerReference: true,
   182  		},
   183  		ChildrenResourcesRule{
   184  			GroupResourceType: GroupResourceType{Group: "kustomize.toolkit.fluxcd.io", Kind: "Kustomization"},
   185  			SubResources: buildSubResources([]*SubResourceSelector{
   186  				{
   187  					ResourceType: ResourceType{APIVersion: "apps/v1", Kind: "Deployment"},
   188  				},
   189  				{
   190  					ResourceType: ResourceType{APIVersion: "apps/v1", Kind: "StatefulSet"},
   191  				},
   192  				{
   193  					ResourceType: ResourceType{APIVersion: "v1", Kind: "ConfigMap"},
   194  				},
   195  				{
   196  					ResourceType: ResourceType{APIVersion: "v1", Kind: "Secret"},
   197  				},
   198  				{
   199  					ResourceType: ResourceType{APIVersion: "v1", Kind: "Service"},
   200  				},
   201  				{
   202  					ResourceType: ResourceType{APIVersion: "v1", Kind: "PersistentVolumeClaim"},
   203  				},
   204  				{
   205  					ResourceType: ResourceType{APIVersion: "networking.k8s.io/v1", Kind: "Ingress"},
   206  				},
   207  				{
   208  					ResourceType: ResourceType{APIVersion: "gateway.networking.k8s.io/v1beta1", Kind: "HTTPRoute"},
   209  				},
   210  				{
   211  					ResourceType: ResourceType{APIVersion: "gateway.networking.k8s.io/v1beta1", Kind: "Gateway"},
   212  				},
   213  				{
   214  					ResourceType: ResourceType{APIVersion: "v1", Kind: "ServiceAccount"},
   215  				},
   216  				{
   217  					ResourceType: ResourceType{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
   218  				},
   219  				{
   220  					ResourceType: ResourceType{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "RoleBinding"},
   221  				},
   222  			}),
   223  			DefaultGenListOptionFunc:      kustomization2AnyListOption,
   224  			DisableFilterByOwnerReference: true,
   225  		},
   226  		ChildrenResourcesRule{
   227  			SubResources: buildSubResources([]*SubResourceSelector{
   228  				{
   229  					ResourceType: ResourceType{APIVersion: "batch/v1", Kind: "Job"},
   230  					listOptions:  cronJobLabelListOption,
   231  				},
   232  			}),
   233  			GroupResourceType: GroupResourceType{Group: "batch", Kind: "CronJob"},
   234  		},
   235  	)
   236  }
   237  
   238  // GroupResourceType define the parent resource type
   239  type GroupResourceType struct {
   240  	Group string `json:"group"`
   241  	Kind  string `json:"kind"`
   242  }
   243  
   244  // ResourceType define the children resource type
   245  type ResourceType struct {
   246  	APIVersion string `json:"apiVersion,omitempty"`
   247  	Kind       string `json:"kind,omitempty"`
   248  }
   249  
   250  // customRule define the customize rule created by user
   251  type customRule struct {
   252  	ParentResourceType   *GroupResourceType `json:"parentResourceType,omitempty"`
   253  	ChildrenResourceType []CustomSelector   `json:"childrenResourceType,omitempty"`
   254  }
   255  
   256  // CustomSelector the custom resource selector configuration in configmap. support set the default label selector policy
   257  type CustomSelector struct {
   258  	ResourceType `json:",inline"`
   259  	// defaultLabelSelector means read the label selector condition from the spec.selector.
   260  	DefaultLabelSelector bool `json:"defaultLabelSelector"`
   261  }
   262  
   263  // ChildrenResourcesRule define the relationShip between parentObject and children resource
   264  type ChildrenResourcesRule struct {
   265  	// GroupResourceType the root resource type
   266  	GroupResourceType GroupResourceType
   267  	// every subResourceType can have a specified genListOptionFunc.
   268  	SubResources *SubResources
   269  	// if specified genListOptionFunc is nil will use use default genListOptionFunc to generate listOption.
   270  	DefaultGenListOptionFunc genListOptionFunc
   271  	// DisableFilterByOwnerReference means don't use parent resource's UID filter the result.
   272  	DisableFilterByOwnerReference bool
   273  }
   274  
   275  func buildSubResources(crs []*SubResourceSelector) *SubResources {
   276  	var cr SubResources = crs
   277  	return &cr
   278  }
   279  
   280  func buildSubResourceSelector(cus CustomSelector) *SubResourceSelector {
   281  	cr := SubResourceSelector{
   282  		ResourceType: cus.ResourceType,
   283  	}
   284  	if cus.DefaultLabelSelector {
   285  		cr.listOptions = defaultWorkloadLabelListOption
   286  	}
   287  	return &cr
   288  }
   289  
   290  // SubResources the sub resource definitions
   291  type SubResources []*SubResourceSelector
   292  
   293  // Get get the sub resource by the resource type
   294  func (c *SubResources) Get(rt ResourceType) *SubResourceSelector {
   295  	for _, r := range *c {
   296  		if r.ResourceType == rt {
   297  			return r
   298  		}
   299  	}
   300  	return nil
   301  }
   302  
   303  // Put add a sub resource to the list
   304  func (c *SubResources) Put(cr *SubResourceSelector) {
   305  	*c = append(*c, cr)
   306  }
   307  
   308  // SubResourceSelector the sub resource selector configuration
   309  type SubResourceSelector struct {
   310  	ResourceType
   311  	listOptions genListOptionFunc
   312  }
   313  
   314  type genListOptionFunc func(unstructured.Unstructured) (client.ListOptions, error)
   315  
   316  // WorkloadUnstructured the workload unstructured, such as Deployment、Job、StatefulSet、ReplicaSet and DaemonSet
   317  type WorkloadUnstructured struct {
   318  	unstructured.Unstructured
   319  }
   320  
   321  // GetSelector get the selector from the field path
   322  func (w *WorkloadUnstructured) GetSelector(fields ...string) (labels.Selector, error) {
   323  	value, exist, err := unstructured.NestedFieldNoCopy(w.Object, fields...)
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  	if !exist {
   328  		return labels.Everything(), nil
   329  	}
   330  	if v, ok := value.(map[string]interface{}); ok {
   331  		var selector v1.LabelSelector
   332  		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(v, &selector); err != nil {
   333  			return nil, err
   334  		}
   335  		return v1.LabelSelectorAsSelector(&selector)
   336  	}
   337  	return labels.Everything(), nil
   338  }
   339  
   340  func (w *WorkloadUnstructured) convertLabel2Selector(fields ...string) (labels.Selector, error) {
   341  	value, exist, err := unstructured.NestedFieldNoCopy(w.Object, fields...)
   342  	if err != nil {
   343  		return nil, err
   344  	}
   345  	if !exist {
   346  		return labels.Everything(), nil
   347  	}
   348  	if v, ok := value.(map[string]interface{}); ok {
   349  		var selector v1.LabelSelector
   350  		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(v, &selector.MatchLabels); err != nil {
   351  			return nil, err
   352  		}
   353  		return v1.LabelSelectorAsSelector(&selector)
   354  	}
   355  	return labels.Everything(), nil
   356  }
   357  
   358  var defaultWorkloadLabelListOption genListOptionFunc = func(obj unstructured.Unstructured) (client.ListOptions, error) {
   359  	workload := WorkloadUnstructured{obj}
   360  	deploySelector, err := workload.GetSelector("spec", "selector")
   361  	if err != nil {
   362  		return client.ListOptions{}, err
   363  	}
   364  	return client.ListOptions{Namespace: obj.GetNamespace(), LabelSelector: deploySelector}, nil
   365  }
   366  
   367  var service2EndpointListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
   368  	svc := v12.Service{}
   369  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &svc)
   370  	if err != nil {
   371  		return client.ListOptions{}, err
   372  	}
   373  	stsSelector, err := v1.LabelSelectorAsSelector(&v1.LabelSelector{MatchLabels: svc.Labels})
   374  	if err != nil {
   375  		return client.ListOptions{}, err
   376  	}
   377  	return client.ListOptions{Namespace: svc.Namespace, LabelSelector: stsSelector}, nil
   378  }
   379  
   380  var cronJobLabelListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
   381  	workload := WorkloadUnstructured{obj}
   382  	cronJobSelector, err := workload.convertLabel2Selector("spec", "jobTemplate", "metadata", "labels")
   383  	if err != nil {
   384  		return client.ListOptions{}, err
   385  	}
   386  	return client.ListOptions{Namespace: obj.GetNamespace(), LabelSelector: cronJobSelector}, nil
   387  }
   388  
   389  var helmRelease2AnyListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
   390  	hrSelector, err := v1.LabelSelectorAsSelector(&v1.LabelSelector{MatchLabels: map[string]string{
   391  		"helm.toolkit.fluxcd.io/name":      obj.GetName(),
   392  		"helm.toolkit.fluxcd.io/namespace": obj.GetNamespace(),
   393  	}})
   394  	if err != nil {
   395  		return client.ListOptions{}, err
   396  	}
   397  	return client.ListOptions{LabelSelector: hrSelector}, nil
   398  }
   399  
   400  var kustomization2AnyListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
   401  	kusSelector, err := v1.LabelSelectorAsSelector(&v1.LabelSelector{MatchLabels: map[string]string{
   402  		"kustomize.toolkit.fluxcd.io/name":      obj.GetName(),
   403  		"kustomize.toolkit.fluxcd.io/namespace": obj.GetNamespace(),
   404  	}})
   405  	if err != nil {
   406  		return client.ListOptions{}, err
   407  	}
   408  	return client.ListOptions{LabelSelector: kusSelector}, nil
   409  }
   410  
   411  type healthyCheckFunc func(obj unstructured.Unstructured) (*types.HealthStatus, error)
   412  
   413  var checkPodStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
   414  	var pod v12.Pod
   415  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pod)
   416  	if err != nil {
   417  		return nil, fmt.Errorf("failed to convert unstructured Pod to typed: %w", err)
   418  	}
   419  
   420  	getFailMessage := func(ctr *v12.ContainerStatus) string {
   421  		if ctr.State.Terminated != nil {
   422  			if ctr.State.Terminated.Message != "" {
   423  				return ctr.State.Terminated.Message
   424  			}
   425  			if ctr.State.Terminated.Reason == "OOMKilled" {
   426  				return ctr.State.Terminated.Reason
   427  			}
   428  			if ctr.State.Terminated.ExitCode != 0 {
   429  				return fmt.Sprintf("container %q failed with exit code %d", ctr.Name, ctr.State.Terminated.ExitCode)
   430  			}
   431  		}
   432  		return ""
   433  	}
   434  
   435  	switch pod.Status.Phase {
   436  	case v12.PodSucceeded:
   437  		return &types.HealthStatus{
   438  			Status:  types.HealthStatusHealthy,
   439  			Reason:  pod.Status.Reason,
   440  			Message: pod.Status.Message,
   441  		}, nil
   442  	case v12.PodRunning:
   443  		switch pod.Spec.RestartPolicy {
   444  		case v12.RestartPolicyAlways:
   445  			// if pod is ready, it is automatically healthy
   446  			if podutils.IsPodReady(&pod) {
   447  				return &types.HealthStatus{
   448  					Status: types.HealthStatusHealthy,
   449  					Reason: "all containers are ready",
   450  				}, nil
   451  			}
   452  			// if it's not ready, check to see if any container terminated, if so, it's unhealthy
   453  			for _, ctrStatus := range pod.Status.ContainerStatuses {
   454  				if ctrStatus.LastTerminationState.Terminated != nil {
   455  					return &types.HealthStatus{
   456  						Status:  types.HealthStatusUnHealthy,
   457  						Reason:  pod.Status.Reason,
   458  						Message: pod.Status.Message,
   459  					}, nil
   460  				}
   461  			}
   462  			// otherwise we are progressing towards a ready state
   463  			return &types.HealthStatus{
   464  				Status:  types.HealthStatusProgressing,
   465  				Reason:  pod.Status.Reason,
   466  				Message: pod.Status.Message,
   467  			}, nil
   468  		case v12.RestartPolicyOnFailure, v12.RestartPolicyNever:
   469  			// pods set with a restart policy of OnFailure or Never, have a finite life.
   470  			// These pods are typically resource hooks. Thus, we consider these as Progressing
   471  			// instead of healthy.
   472  			return &types.HealthStatus{
   473  				Status:  types.HealthStatusProgressing,
   474  				Reason:  pod.Status.Reason,
   475  				Message: pod.Status.Message,
   476  			}, nil
   477  		}
   478  	case v12.PodPending:
   479  		return &types.HealthStatus{
   480  			Status:  types.HealthStatusProgressing,
   481  			Message: pod.Status.Message,
   482  		}, nil
   483  	case v12.PodFailed:
   484  		if pod.Status.Message != "" {
   485  			// Pod has a nice error message. Use that.
   486  			return &types.HealthStatus{Status: types.HealthStatusUnHealthy, Message: pod.Status.Message}, nil
   487  		}
   488  		for _, ctr := range append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...) {
   489  			if msg := getFailMessage(ctr.DeepCopy()); msg != "" {
   490  				return &types.HealthStatus{Status: types.HealthStatusUnHealthy, Message: msg}, nil
   491  			}
   492  		}
   493  		return &types.HealthStatus{Status: types.HealthStatusUnHealthy, Message: ""}, nil
   494  	default:
   495  	}
   496  	return &types.HealthStatus{
   497  		Status:  types.HealthStatusUnKnown,
   498  		Reason:  string(pod.Status.Phase),
   499  		Message: pod.Status.Message,
   500  	}, nil
   501  }
   502  
   503  var checkHelmReleaseStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
   504  	helmRelease := &helmreleaseapi.HelmRelease{}
   505  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &helmRelease)
   506  	if err != nil {
   507  		return nil, fmt.Errorf("failed to convert unstructured helmRelease to typed: %w", err)
   508  	}
   509  	if len(helmRelease.Status.Conditions) != 0 {
   510  		for _, condition := range helmRelease.Status.Conditions {
   511  			if condition.Type == "Ready" {
   512  				if condition.Status == v1.ConditionTrue {
   513  					return &types.HealthStatus{
   514  						Status: types.HealthStatusHealthy,
   515  					}, nil
   516  				}
   517  				return &types.HealthStatus{
   518  					Status:  types.HealthStatusUnHealthy,
   519  					Message: condition.Message,
   520  				}, nil
   521  			}
   522  		}
   523  	}
   524  	return &types.HealthStatus{
   525  		Status: types.HealthStatusUnKnown,
   526  	}, nil
   527  }
   528  
   529  var checkHelmRepoStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
   530  	helmRepo := helmrepoapi.HelmRepository{}
   531  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &helmRepo)
   532  	if err != nil {
   533  		return nil, fmt.Errorf("failed to convert unstructured helmRelease to typed: %w", err)
   534  	}
   535  	if len(helmRepo.Status.Conditions) != 0 {
   536  		for _, condition := range helmRepo.Status.Conditions {
   537  			if condition.Type == "Ready" {
   538  				if condition.Status == v1.ConditionTrue {
   539  					return &types.HealthStatus{
   540  						Status:  types.HealthStatusHealthy,
   541  						Message: condition.Message,
   542  					}, nil
   543  				}
   544  				return &types.HealthStatus{
   545  					Status:  types.HealthStatusUnHealthy,
   546  					Message: condition.Message,
   547  				}, nil
   548  			}
   549  		}
   550  	}
   551  	return &types.HealthStatus{
   552  		Status: types.HealthStatusUnKnown,
   553  	}, nil
   554  }
   555  
   556  var checkReplicaSetStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
   557  	replicaSet := appsv1.ReplicaSet{}
   558  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &replicaSet)
   559  	if err != nil {
   560  		return nil, fmt.Errorf("failed to convert unstructured ReplicaSet to typed: %w", err)
   561  	}
   562  	if replicaSet.Generation <= replicaSet.Status.ObservedGeneration {
   563  		cond := getAppsv1ReplicaSetCondition(replicaSet.Status, appsv1.ReplicaSetReplicaFailure)
   564  		if cond != nil && cond.Status == v12.ConditionTrue {
   565  			return &types.HealthStatus{
   566  				Status:  types.HealthStatusUnHealthy,
   567  				Reason:  cond.Reason,
   568  				Message: cond.Message,
   569  			}, nil
   570  		} else if replicaSet.Spec.Replicas != nil && replicaSet.Status.AvailableReplicas < *replicaSet.Spec.Replicas {
   571  			return &types.HealthStatus{
   572  				Status:  types.HealthStatusProgressing,
   573  				Message: fmt.Sprintf("Waiting for rollout to finish: %d out of %d new replicas are available...", replicaSet.Status.AvailableReplicas, *replicaSet.Spec.Replicas),
   574  			}, nil
   575  		}
   576  	} else {
   577  		return &types.HealthStatus{
   578  			Status:  types.HealthStatusProgressing,
   579  			Message: "Waiting for rollout to finish: observed replica set generation less then desired generation",
   580  		}, nil
   581  	}
   582  
   583  	return &types.HealthStatus{
   584  		Status: types.HealthStatusHealthy,
   585  	}, nil
   586  }
   587  
   588  func getAppsv1ReplicaSetCondition(status appsv1.ReplicaSetStatus, condType appsv1.ReplicaSetConditionType) *appsv1.ReplicaSetCondition {
   589  	for i := range status.Conditions {
   590  		c := status.Conditions[i]
   591  		if c.Type == condType {
   592  			return &c
   593  		}
   594  	}
   595  	return nil
   596  }
   597  
   598  var checkPVCHealthStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
   599  	pvc := v12.PersistentVolumeClaim{}
   600  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pvc)
   601  	if err != nil {
   602  		return nil, fmt.Errorf("failed to convert unstructured PVC to typed: %w", err)
   603  	}
   604  	var status types.HealthStatusCode
   605  	switch pvc.Status.Phase {
   606  	case v12.ClaimLost:
   607  		status = types.HealthStatusUnHealthy
   608  	case v12.ClaimPending:
   609  		status = types.HealthStatusProgressing
   610  	case v12.ClaimBound:
   611  		status = types.HealthStatusHealthy
   612  	default:
   613  		status = types.HealthStatusUnKnown
   614  	}
   615  	return &types.HealthStatus{Status: status}, nil
   616  }
   617  
   618  var checkServiceStatus = func(obj unstructured.Unstructured) (*types.HealthStatus, error) {
   619  	svc := v12.Service{}
   620  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &svc)
   621  	if err != nil {
   622  		return nil, fmt.Errorf("failed to convert unstructured service to typed: %w", err)
   623  	}
   624  	health := types.HealthStatus{Status: types.HealthStatusHealthy}
   625  	if svc.Spec.Type == v12.ServiceTypeLoadBalancer {
   626  		if len(svc.Status.LoadBalancer.Ingress) > 0 {
   627  			health.Status = types.HealthStatusHealthy
   628  		} else {
   629  			health.Status = types.HealthStatusProgressing
   630  		}
   631  	}
   632  	return &health, nil
   633  }
   634  
   635  // CheckResourceStatus return object status data
   636  func CheckResourceStatus(obj unstructured.Unstructured) (*types.HealthStatus, error) {
   637  	group := obj.GroupVersionKind().Group
   638  	kind := obj.GroupVersionKind().Kind
   639  	var checkFunc healthyCheckFunc
   640  	switch group {
   641  	case "":
   642  		switch kind {
   643  		case "Pod":
   644  			checkFunc = checkPodStatus
   645  		case "Service":
   646  			checkFunc = checkServiceStatus
   647  		case "PersistentVolumeClaim":
   648  			checkFunc = checkPVCHealthStatus
   649  		}
   650  	case "apps":
   651  		switch kind {
   652  		case "ReplicaSet":
   653  			checkFunc = checkReplicaSetStatus
   654  		default:
   655  		}
   656  	case "helm.toolkit.fluxcd.io":
   657  		switch kind {
   658  		case "HelmRelease":
   659  			checkFunc = checkHelmReleaseStatus
   660  		default:
   661  		}
   662  	case "source.toolkit.fluxcd.io":
   663  		switch kind {
   664  		case "HelmRepository":
   665  			checkFunc = checkHelmRepoStatus
   666  		default:
   667  		}
   668  	default:
   669  	}
   670  	if checkFunc != nil {
   671  		return checkFunc(obj)
   672  	}
   673  	return &types.HealthStatus{Status: types.HealthStatusHealthy}, nil
   674  }
   675  
   676  type additionalInfoFunc func(obj unstructured.Unstructured) (map[string]interface{}, error)
   677  
   678  func additionalInfo(obj unstructured.Unstructured) (map[string]interface{}, error) {
   679  	group := obj.GroupVersionKind().Group
   680  	kind := obj.GroupVersionKind().Kind
   681  	var infoFunc additionalInfoFunc
   682  	switch group {
   683  	case "":
   684  		switch kind {
   685  		case "Pod":
   686  			infoFunc = podAdditionalInfo
   687  		case "Service":
   688  			infoFunc = svcAdditionalInfo
   689  		}
   690  	case "apps":
   691  		switch kind {
   692  		case "Deployment":
   693  			infoFunc = deploymentAdditionalInfo
   694  		case "StatefulSet":
   695  			infoFunc = statefulSetAdditionalInfo
   696  		default:
   697  		}
   698  	default:
   699  	}
   700  	if infoFunc != nil {
   701  		return infoFunc(obj)
   702  	}
   703  	return nil, nil
   704  }
   705  
   706  func svcAdditionalInfo(obj unstructured.Unstructured) (map[string]interface{}, error) {
   707  	svc := v12.Service{}
   708  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &svc)
   709  	if err != nil {
   710  		return nil, fmt.Errorf("failed to convert unstructured svc to typed: %w", err)
   711  	}
   712  	if svc.Spec.Type == v12.ServiceTypeLoadBalancer {
   713  		var eip string
   714  		for _, ingress := range svc.Status.LoadBalancer.Ingress {
   715  			if len(ingress.IP) != 0 {
   716  				eip = ingress.IP
   717  			}
   718  		}
   719  		if len(eip) == 0 {
   720  			eip = "pending"
   721  		}
   722  		return map[string]interface{}{
   723  			"EIP": eip,
   724  		}, nil
   725  	}
   726  	return nil, nil
   727  }
   728  
   729  // the logic of this func totaly copy from the source-code of kubernetes tableConvertor
   730  // https://github.com/kubernetes/kubernetes/blob/ea0764452222146c47ec826977f49d7001b0ea8c/pkg/printers/internalversion/printers.go#L740
   731  // The result is same with the output of kubectl.
   732  // nolint
   733  func podAdditionalInfo(obj unstructured.Unstructured) (map[string]interface{}, error) {
   734  	pod := v12.Pod{}
   735  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pod)
   736  	if err != nil {
   737  		return nil, fmt.Errorf("failed to convert unstructured Pod to typed: %w", err)
   738  	}
   739  
   740  	hasPodReadyCondition := func(conditions []v12.PodCondition) bool {
   741  		for _, condition := range conditions {
   742  			if condition.Type == v12.PodReady && condition.Status == v12.ConditionTrue {
   743  				return true
   744  			}
   745  		}
   746  		return false
   747  	}
   748  
   749  	restarts := 0
   750  	totalContainers := len(pod.Spec.Containers)
   751  	readyContainers := 0
   752  
   753  	reason := string(pod.Status.Phase)
   754  	if pod.Status.Reason != "" {
   755  		reason = pod.Status.Reason
   756  	}
   757  
   758  	initializing := false
   759  	for i := range pod.Status.InitContainerStatuses {
   760  		container := pod.Status.InitContainerStatuses[i]
   761  		restarts += int(container.RestartCount)
   762  		switch {
   763  		case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0:
   764  			continue
   765  		case container.State.Terminated != nil:
   766  			// initialization is failed
   767  			if len(container.State.Terminated.Reason) == 0 {
   768  				if container.State.Terminated.Signal != 0 {
   769  					reason = fmt.Sprintf("Init:Signal:%d", container.State.Terminated.Signal)
   770  				} else {
   771  					reason = fmt.Sprintf("Init:ExitCode:%d", container.State.Terminated.ExitCode)
   772  				}
   773  			} else {
   774  				reason = "Init:" + container.State.Terminated.Reason
   775  			}
   776  			initializing = true
   777  		case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing":
   778  			reason = "Init:" + container.State.Waiting.Reason
   779  			initializing = true
   780  		default:
   781  			reason = fmt.Sprintf("Init:%d/%d", i, len(pod.Spec.InitContainers))
   782  			initializing = true
   783  		}
   784  		break
   785  	}
   786  	if !initializing {
   787  		restarts = 0
   788  		hasRunning := false
   789  		for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
   790  			container := pod.Status.ContainerStatuses[i]
   791  
   792  			restarts += int(container.RestartCount)
   793  			if container.State.Waiting != nil && container.State.Waiting.Reason != "" {
   794  				reason = container.State.Waiting.Reason
   795  			} else if container.State.Terminated != nil && container.State.Terminated.Reason != "" {
   796  				reason = container.State.Terminated.Reason
   797  			} else if container.State.Terminated != nil && container.State.Terminated.Reason == "" {
   798  				if container.State.Terminated.Signal != 0 {
   799  					reason = fmt.Sprintf("Signal:%d", container.State.Terminated.Signal)
   800  				} else {
   801  					reason = fmt.Sprintf("ExitCode:%d", container.State.Terminated.ExitCode)
   802  				}
   803  			} else if container.Ready && container.State.Running != nil {
   804  				hasRunning = true
   805  				readyContainers++
   806  			}
   807  		}
   808  
   809  		// change pod status back to "Running" if there is at least one container still reporting as "Running" status
   810  		if reason == "Completed" && hasRunning {
   811  			if hasPodReadyCondition(pod.Status.Conditions) {
   812  				reason = "Running"
   813  			} else {
   814  				reason = "NotReady"
   815  			}
   816  		}
   817  	}
   818  
   819  	if pod.DeletionTimestamp != nil && pod.Status.Reason == "NodeLost" {
   820  		reason = "Unknown"
   821  	} else if pod.DeletionTimestamp != nil {
   822  		reason = "Terminating"
   823  	}
   824  	return map[string]interface{}{
   825  		"Ready":    fmt.Sprintf("%d/%d", readyContainers, totalContainers),
   826  		"Status":   reason,
   827  		"Restarts": restarts,
   828  		"Age":      translateTimestampSince(pod.CreationTimestamp),
   829  	}, nil
   830  }
   831  
   832  func deploymentAdditionalInfo(obj unstructured.Unstructured) (map[string]interface{}, error) {
   833  	deployment := appsv1.Deployment{}
   834  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deployment)
   835  	if err != nil {
   836  		return nil, fmt.Errorf("failed to convert unstructured Deployment to typed: %w", err)
   837  	}
   838  
   839  	readyReplicas := deployment.Status.ReadyReplicas
   840  	desiredReplicas := deployment.Spec.Replicas
   841  	updatedReplicas := deployment.Status.UpdatedReplicas
   842  	availableReplicas := deployment.Status.AvailableReplicas
   843  
   844  	return map[string]interface{}{
   845  		"Ready":     fmt.Sprintf("%d/%d", readyReplicas, *desiredReplicas),
   846  		"Update":    updatedReplicas,
   847  		"Available": availableReplicas,
   848  		"Age":       translateTimestampSince(deployment.CreationTimestamp),
   849  	}, nil
   850  }
   851  
   852  func statefulSetAdditionalInfo(obj unstructured.Unstructured) (map[string]interface{}, error) {
   853  	statefulSet := appsv1.StatefulSet{}
   854  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &statefulSet)
   855  	if err != nil {
   856  		return nil, fmt.Errorf("failed to convert unstructured StatefulSet to typed: %w", err)
   857  	}
   858  
   859  	readyReplicas := statefulSet.Status.ReadyReplicas
   860  	desiredReplicas := statefulSet.Spec.Replicas
   861  
   862  	return map[string]interface{}{
   863  		"Ready": fmt.Sprintf("%d/%d", readyReplicas, *desiredReplicas),
   864  		"Age":   translateTimestampSince(statefulSet.CreationTimestamp),
   865  	}, nil
   866  }
   867  
   868  func fetchObjectWithResourceTreeNode(ctx context.Context, cluster string, k8sClient client.Client, resource types.ResourceTreeNode) (*unstructured.Unstructured, error) {
   869  	o := unstructured.Unstructured{}
   870  	o.SetAPIVersion(resource.APIVersion)
   871  	o.SetKind(resource.Kind)
   872  	o.SetNamespace(resource.Namespace)
   873  	o.SetName(resource.Name)
   874  	err := k8sClient.Get(multicluster.ContextWithClusterName(ctx, cluster), types2.NamespacedName{Namespace: resource.Namespace, Name: resource.Name}, &o)
   875  	if err != nil {
   876  		return nil, err
   877  	}
   878  	return &o, nil
   879  }
   880  
   881  func listItemByRule(clusterCTX context.Context, k8sClient client.Client, resource ResourceType,
   882  	parentObject unstructured.Unstructured, specifiedFunc genListOptionFunc, defaultFunc genListOptionFunc, disableFilterByOwner bool) ([]unstructured.Unstructured, error) {
   883  
   884  	itemList := unstructured.UnstructuredList{}
   885  	itemList.SetAPIVersion(resource.APIVersion)
   886  	itemList.SetKind(fmt.Sprintf("%sList", resource.Kind))
   887  	var err error
   888  	if specifiedFunc == nil && defaultFunc == nil {
   889  		// if the relationShip between parent and child hasn't defined by any genListOption, list all subResource and filter by ownerReference UID
   890  		err = k8sClient.List(clusterCTX, &itemList)
   891  		if err != nil {
   892  			return nil, err
   893  		}
   894  		var res []unstructured.Unstructured
   895  		for _, item := range itemList.Items {
   896  			for _, reference := range item.GetOwnerReferences() {
   897  				if reference.UID == parentObject.GetUID() {
   898  					res = append(res, item)
   899  				}
   900  			}
   901  		}
   902  		sort.Slice(res, func(i, j int) bool {
   903  			return res[i].GetName() < res[j].GetName()
   904  		})
   905  		return res, nil
   906  	}
   907  	var listOptions client.ListOptions
   908  	if specifiedFunc != nil {
   909  		//  specified func will override the default func
   910  		listOptions, err = specifiedFunc(parentObject)
   911  		if err != nil {
   912  			return nil, err
   913  		}
   914  	} else {
   915  		listOptions, err = defaultFunc(parentObject)
   916  		if err != nil {
   917  			return nil, err
   918  		}
   919  	}
   920  	err = k8sClient.List(clusterCTX, &itemList, &listOptions)
   921  	if err != nil {
   922  		return nil, err
   923  	}
   924  	if !disableFilterByOwner {
   925  		var res []unstructured.Unstructured
   926  		for _, item := range itemList.Items {
   927  			if len(item.GetOwnerReferences()) == 0 {
   928  				res = append(res, item)
   929  			}
   930  			for _, reference := range item.GetOwnerReferences() {
   931  				if reference.UID == parentObject.GetUID() {
   932  					res = append(res, item)
   933  				}
   934  			}
   935  		}
   936  		return res, nil
   937  	}
   938  	sort.Slice(itemList.Items, func(i, j int) bool {
   939  		return itemList.Items[i].GetName() < itemList.Items[j].GetName()
   940  	})
   941  	return itemList.Items, nil
   942  }
   943  
   944  func iterateListSubResources(ctx context.Context, cluster string, k8sClient client.Client, parentResource types.ResourceTreeNode, depth int, filter func(node types.ResourceTreeNode) bool) ([]*types.ResourceTreeNode, error) {
   945  	if depth > maxDepth() {
   946  		klog.Warningf("listing application resource tree has reached the max-depth %d parentObject is %v", depth, parentResource)
   947  		return nil, nil
   948  	}
   949  	parentObject, err := fetchObjectWithResourceTreeNode(ctx, cluster, k8sClient, parentResource)
   950  	if err != nil {
   951  		return nil, err
   952  	}
   953  	group := parentObject.GetObjectKind().GroupVersionKind().Group
   954  	kind := parentObject.GetObjectKind().GroupVersionKind().Kind
   955  
   956  	if rule, ok := globalRule.GetRule(GroupResourceType{Group: group, Kind: kind}); ok {
   957  		var resList []*types.ResourceTreeNode
   958  		for i := range *rule.SubResources {
   959  			resource := (*rule.SubResources)[i].ResourceType
   960  			specifiedFunc := (*rule.SubResources)[i].listOptions
   961  
   962  			clusterCTX := multicluster.ContextWithClusterName(ctx, cluster)
   963  			items, err := listItemByRule(clusterCTX, k8sClient, resource, *parentObject, specifiedFunc, rule.DefaultGenListOptionFunc, rule.DisableFilterByOwnerReference)
   964  			if err != nil {
   965  				if meta.IsNoMatchError(err) || runtime.IsNotRegisteredError(err) || kerrors.IsNotFound(err) {
   966  					klog.Warningf("ignore list resources: %s as %v", resource.Kind, err)
   967  					continue
   968  				}
   969  				return nil, err
   970  			}
   971  			for i, item := range items {
   972  				rtn := types.ResourceTreeNode{
   973  					APIVersion: item.GetAPIVersion(),
   974  					Kind:       item.GroupVersionKind().Kind,
   975  					Namespace:  item.GetNamespace(),
   976  					Name:       item.GetName(),
   977  					UID:        item.GetUID(),
   978  					Cluster:    cluster,
   979  					Object:     items[i],
   980  				}
   981  				if _, ok := globalRule.GetRule(GroupResourceType{Group: item.GetObjectKind().GroupVersionKind().Group, Kind: item.GetObjectKind().GroupVersionKind().Kind}); ok {
   982  					childrenRes, err := iterateListSubResources(ctx, cluster, k8sClient, rtn, depth+1, filter)
   983  					if err != nil {
   984  						return nil, err
   985  					}
   986  					rtn.LeafNodes = childrenRes
   987  				}
   988  				if !filter(rtn) && len(rtn.LeafNodes) == 0 {
   989  					continue
   990  				}
   991  				healthStatus, err := CheckResourceStatus(item)
   992  				if err != nil {
   993  					return nil, err
   994  				}
   995  				rtn.HealthStatus = *healthStatus
   996  				addInfo, err := additionalInfo(item)
   997  				if err != nil {
   998  					return nil, err
   999  				}
  1000  				rtn.CreationTimestamp = item.GetCreationTimestamp().Time
  1001  				if !item.GetDeletionTimestamp().IsZero() {
  1002  					rtn.DeletionTimestamp = item.GetDeletionTimestamp().Time
  1003  				}
  1004  				rtn.AdditionalInfo = addInfo
  1005  				resList = append(resList, &rtn)
  1006  			}
  1007  		}
  1008  		return resList, nil
  1009  	}
  1010  	return nil, nil
  1011  }
  1012  
  1013  // mergeCustomRules merge user defined resource topology rules with the system ones
  1014  func mergeCustomRules(ctx context.Context, k8sClient client.Client) error {
  1015  	rulesList := v12.ConfigMapList{}
  1016  	if err := k8sClient.List(ctx, &rulesList, client.InNamespace(velatypes.DefaultKubeVelaNS), client.HasLabels{oam.LabelResourceRules}); err != nil {
  1017  		return client.IgnoreNotFound(err)
  1018  	}
  1019  	for _, item := range rulesList.Items {
  1020  		ruleStr := item.Data[relationshipKey]
  1021  		var (
  1022  			customRules []*customRule
  1023  			format      string
  1024  			err         error
  1025  		)
  1026  		if item.Labels != nil {
  1027  			format = item.Labels[oam.LabelResourceRuleFormat]
  1028  		}
  1029  		switch format {
  1030  		case oam.ResourceTopologyFormatJSON:
  1031  			err = json.Unmarshal([]byte(ruleStr), &customRules)
  1032  		case oam.ResourceTopologyFormatYAML, "":
  1033  			err = yaml.Unmarshal([]byte(ruleStr), &customRules)
  1034  		}
  1035  		if err != nil {
  1036  			// don't let one miss-config configmap brake whole process
  1037  			klog.Errorf("relationship rule configmap %s miss config %v", item.Name, err)
  1038  			continue
  1039  		}
  1040  		for _, rule := range customRules {
  1041  
  1042  			if cResource, ok := globalRule.GetRule(*rule.ParentResourceType); ok {
  1043  				for i, resourceType := range rule.ChildrenResourceType {
  1044  					if cResource.SubResources.Get(resourceType.ResourceType) == nil {
  1045  						cResource.SubResources.Put(buildSubResourceSelector(rule.ChildrenResourceType[i]))
  1046  					}
  1047  				}
  1048  			} else {
  1049  				var subResources []*SubResourceSelector
  1050  				for i := range rule.ChildrenResourceType {
  1051  					subResources = append(subResources, buildSubResourceSelector(rule.ChildrenResourceType[i]))
  1052  				}
  1053  				globalRule = append(globalRule, ChildrenResourcesRule{
  1054  					GroupResourceType:        *rule.ParentResourceType,
  1055  					DefaultGenListOptionFunc: nil,
  1056  					SubResources:             buildSubResources(subResources)})
  1057  			}
  1058  		}
  1059  	}
  1060  	return nil
  1061  }
  1062  
  1063  func translateTimestampSince(timestamp v1.Time) string {
  1064  	if timestamp.IsZero() {
  1065  		return "<unknown>"
  1066  	}
  1067  
  1068  	return duration.HumanDuration(time.Since(timestamp.Time))
  1069  }
  1070  
  1071  // check if max depth is provided or return the default max depth
  1072  func maxDepth() int {
  1073  	maxDepth, err := strconv.Atoi(os.Getenv("KUBEVELA_QUERYTREE_MAXDEPTH"))
  1074  	if err != nil || maxDepth <= 0 {
  1075  		return DefaultMaxDepth
  1076  	}
  1077  
  1078  	return maxDepth
  1079  }