diff --git a/pkg/velaql/providers/query/collector.go b/pkg/velaql/providers/query/collector.go index 9f08dabc2..bf5397afe 100644 --- a/pkg/velaql/providers/query/collector.go +++ b/pkg/velaql/providers/query/collector.go @@ -21,6 +21,7 @@ import ( "reflect" "sync" + "github.com/hashicorp/go-version" kruise "github.com/openkruise/kruise-api/apps/v1alpha1" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" @@ -56,6 +57,8 @@ func NewAppCollector(cli client.Client, opt Option) *AppCollector { } } +const velaVersionNumberToUpgradeVelaQL = "v1.2.0-rc.1" + // CollectResourceFromApp collect resources created by application func (c *AppCollector) CollectResourceFromApp() ([]Resource, error) { ctx := context.Background() @@ -64,7 +67,29 @@ func (c *AppCollector) CollectResourceFromApp() ([]Resource, error) { if err := c.k8sClient.Get(ctx, appKey, app); err != nil { return nil, err } + var currentVersionNumber string + if annotations := app.GetAnnotations(); annotations != nil && annotations[oam.AnnotationKubeVelaVersion] != "" { + currentVersionNumber = annotations[oam.AnnotationKubeVelaVersion] + } + velaVersionToUpgradeVelaQL, _ := version.NewVersion(velaVersionNumberToUpgradeVelaQL) + currentVersion, err := version.NewVersion(currentVersionNumber) + if err != nil { + resources, err := c.FindResourceFromResourceTrackerSpec(app) + if err != nil { + return c.FindResourceFromAppliedResourcesField(app) + } + return resources, nil + } + if velaVersionToUpgradeVelaQL.GreaterThan(currentVersion) { + return c.FindResourceFromAppliedResourcesField(app) + } + return c.FindResourceFromResourceTrackerSpec(app) +} + +// FindResourceFromResourceTrackerSpec find resources from ResourceTracker spec +func (c *AppCollector) FindResourceFromResourceTrackerSpec(app *v1beta1.Application) ([]Resource, error) { + ctx := context.Background() rootRT, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, c.k8sClient, app) if err != nil { return nil, err @@ -74,8 +99,8 @@ func (c *AppCollector) CollectResourceFromApp() ([]Resource, error) { for _, rt := range append(historyRTs, rootRT, currentRT) { if rt != nil { for _, managedResource := range rt.Spec.ManagedResources { - if isResourceInTargetCluster(c.opt.Filter, managedResource) && - isResourceInTargetComponent(c.opt.Filter, managedResource) { + if isResourceInTargetCluster(c.opt.Filter, managedResource.ClusterObjectReference) && + isResourceInTargetComponent(c.opt.Filter, managedResource.Component) { managedResources[managedResource.ClusterObjectReference] = true } } @@ -107,6 +132,49 @@ func (c *AppCollector) CollectResourceFromApp() ([]Resource, error) { return resources, nil } +// FindResourceFromAppliedResourcesField find resources from AppliedResources field +func (c *AppCollector) FindResourceFromAppliedResourcesField(app *v1beta1.Application) ([]Resource, error) { + resources := make([]Resource, 0, len(app.Spec.Components)) + for _, rsrcRef := range app.Status.AppliedResources { + if !isResourceInTargetCluster(c.opt.Filter, rsrcRef) { + continue + } + compName, obj, err := getObjectCreatedByComponent(c.k8sClient, rsrcRef.ObjectReference, rsrcRef.Cluster) + if err != nil { + return nil, err + } + if len(compName) != 0 && isResourceInTargetComponent(c.opt.Filter, compName) { + resources = append(resources, Resource{ + Component: compName, + Revision: obj.GetLabels()[oam.LabelAppRevision], + Cluster: rsrcRef.Cluster, + Object: obj, + }) + } + } + if len(resources) == 0 { + return nil, errors.Errorf("fail to find resources created by application: %v", c.opt.Name) + } + return resources, nil +} + +// getObjectCreatedByComponent get k8s obj created by components +func getObjectCreatedByComponent(cli client.Client, objRef corev1.ObjectReference, cluster string) (string, *unstructured.Unstructured, error) { + ctx := multicluster.ContextWithClusterName(context.Background(), cluster) + obj := new(unstructured.Unstructured) + obj.SetGroupVersionKind(objRef.GroupVersionKind()) + obj.SetNamespace(objRef.Namespace) + obj.SetName(objRef.Name) + if err := cli.Get(ctx, client.ObjectKeyFromObject(obj), obj); err != nil { + if kerrors.IsNotFound(err) { + return "", nil, nil + } + return "", nil, err + } + componentName := obj.GetLabels()[oam.LabelAppComponent] + return componentName, obj, nil +} + var standardWorkloads = []schema.GroupVersionKind{ appsv1.SchemeGroupVersion.WithKind(reflect.TypeOf(appsv1.Deployment{}).Name()), appsv1.SchemeGroupVersion.WithKind(reflect.TypeOf(appsv1.ReplicaSet{}).Name()), @@ -316,22 +384,22 @@ func getEventFieldSelector(obj *unstructured.Unstructured) fields.Selector { return field.AsSelector() } -func isResourceInTargetCluster(opt FilterOption, managedResource v1beta1.ManagedResource) bool { +func isResourceInTargetCluster(opt FilterOption, resource common.ClusterObjectReference) bool { if opt.Cluster == "" && opt.ClusterNamespace == "" { return true } - if opt.Cluster == managedResource.Cluster && opt.ClusterNamespace == managedResource.ObjectReference.Namespace { + if opt.Cluster == resource.Cluster && opt.ClusterNamespace == resource.ObjectReference.Namespace { return true } return false } -func isResourceInTargetComponent(opt FilterOption, managedResource v1beta1.ManagedResource) bool { - if len(opt.Components) == 0 { +func isResourceInTargetComponent(opt FilterOption, componentName string) bool { + if len(opt.Components) == 0 && len(componentName) != 0 { return true } for _, component := range opt.Components { - if component == managedResource.Component { + if component == componentName { return true } } diff --git a/pkg/velaql/providers/query/handler_test.go b/pkg/velaql/providers/query/handler_test.go index fbbbbe8f7..4ee26f3ac 100644 --- a/pkg/velaql/providers/query/handler_test.go +++ b/pkg/velaql/providers/query/handler_test.go @@ -19,6 +19,7 @@ package query import ( "encoding/json" "fmt" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -26,8 +27,11 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" + "github.com/oam-dev/kubevela/apis/core.oam.dev/common" + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" "github.com/oam-dev/kubevela/pkg/cue/model/value" "github.com/oam-dev/kubevela/pkg/oam" "github.com/oam-dev/kubevela/pkg/oam/util" @@ -63,6 +67,126 @@ var _ = Describe("Test Query Provider", func() { }) Context("Test ListResourcesInApp", func() { + It("Test list latest resources created by application", func() { + namespace := "test" + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} + Expect(k8sClient.Create(ctx, &ns)).Should(BeNil()) + + app := v1beta1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + Annotations: map[string]string{ + "oam.dev/kubevela-version": "v1.2.0-beta.2", + }, + }, + Spec: v1beta1.ApplicationSpec{ + Components: []common.ApplicationComponent{{ + Name: "web", + Type: "webservice", + Properties: util.Object2RawExtension(map[string]string{ + "image": "busybox", + }), + Traits: []common.ApplicationTrait{{ + Type: "expose", + Properties: util.Object2RawExtension(map[string]interface{}{ + "ports": []int{8000}, + }), + }}, + }}, + }, + } + + Expect(k8sClient.Create(ctx, &app)).Should(BeNil()) + oldApp := new(v1beta1.Application) + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&app), oldApp)).Should(BeNil()) + oldApp.Status.LatestRevision = &common.Revision{ + Revision: 1, + } + oldApp.Status.AppliedResources = []common.ClusterObjectReference{{ + Cluster: "", + Creator: "workflow", + ObjectReference: corev1.ObjectReference{ + APIVersion: "v1", + Kind: "Service", + Namespace: namespace, + Name: "web", + }, + }, { + Cluster: "", + Creator: "workflow", + ObjectReference: corev1.ObjectReference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Namespace: namespace, + Name: "web", + }, + }} + Eventually(func() error { + err := k8sClient.Status().Update(ctx, oldApp) + if err != nil { + return err + } + return nil + }, 300*time.Microsecond, 3*time.Second).Should(BeNil()) + + appDeploy := baseDeploy.DeepCopy() + appDeploy.SetName("web") + appDeploy.SetNamespace(namespace) + appDeploy.SetLabels(map[string]string{ + oam.LabelAppComponent: "web", + oam.LabelAppRevision: "test-v1", + }) + Expect(k8sClient.Create(ctx, appDeploy)).Should(BeNil()) + + appService := baseService.DeepCopy() + appService.SetName("web") + appService.SetNamespace(namespace) + appService.SetLabels(map[string]string{ + oam.LabelAppComponent: "web", + oam.LabelAppRevision: "test-v1", + }) + Expect(k8sClient.Create(ctx, appService)).Should(BeNil()) + + prd := provider{cli: k8sClient} + opt := `app: { + name: "test" + namespace: "test" + filter: { + cluster: "", + clusterNamespace: "test", + components: ["web"] + } + }` + v, err := value.NewValue(opt, nil, "") + Expect(err).Should(BeNil()) + Expect(prd.ListResourcesInApp(nil, v, nil)).Should(BeNil()) + + appResList := new(AppResourcesList) + Expect(v.UnmarshalTo(appResList)).Should(BeNil()) + + Expect(len(appResList.List)).Should(Equal(2)) + + Expect(appResList.List[0].Object.GroupVersionKind()).Should(Equal(oldApp.Status.AppliedResources[0].GroupVersionKind())) + Expect(appResList.List[1].Object.GroupVersionKind()).Should(Equal(oldApp.Status.AppliedResources[1].GroupVersionKind())) + + updateApp := new(v1beta1.Application) + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&app), updateApp)).Should(BeNil()) + + updateApp.ObjectMeta.Annotations = map[string]string{ + "oam.dev/kubevela-version": "master", + } + Expect(k8sClient.Update(ctx, updateApp)).Should(BeNil()) + newValue, err := value.NewValue(opt, nil, "") + Expect(err).Should(BeNil()) + Expect(prd.ListResourcesInApp(nil, newValue, nil)).Should(BeNil()) + newAppResList := new(AppResourcesList) + Expect(v.UnmarshalTo(newAppResList)).Should(BeNil()) + Expect(len(newAppResList.List)).Should(Equal(2)) + Expect(newAppResList.List[0].Object.GroupVersionKind()).Should(Equal(updateApp.Status.AppliedResources[0].GroupVersionKind())) + Expect(newAppResList.List[1].Object.GroupVersionKind()).Should(Equal(updateApp.Status.AppliedResources[1].GroupVersionKind())) + }) + It("Test list resource with incomplete parameter", func() { optWithoutApp := "" prd := provider{cli: k8sClient}