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 }