diff --git a/cmd/ks-apiserver/app/server.go b/cmd/ks-apiserver/app/server.go index 3619783cd0553b80fc8db9584518a8d647525625..e29f66fa95d8ad29b4e142272c6c7a5fefc35d14 100644 --- a/cmd/ks-apiserver/app/server.go +++ b/cmd/ks-apiserver/app/server.go @@ -291,6 +291,26 @@ func WaitForResourceSync(stopCh <-chan struct{}) error { ksInformerFactory.Start(stopCh) ksInformerFactory.WaitForCacheSync(stopCh) + appInformerFactory := informers.AppSharedInformerFactory() + + appGVRs := []schema.GroupVersionResource{ + {Group: "app.k8s.io", Version: "v1beta1", Resource: "applications"}, + } + + for _, gvr := range appGVRs { + if !isResourceExists(gvr) { + klog.Warningf("resource %s not exists in the cluster", gvr) + } else { + _, err := appInformerFactory.ForResource(gvr) + if err != nil { + return err + } + } + } + + appInformerFactory.Start(stopCh) + appInformerFactory.WaitForCacheSync(stopCh) + klog.V(0).Info("Finished caching objects") return nil diff --git a/pkg/informers/informers.go b/pkg/informers/informers.go index 9e72f2226a3b554813853c3145c3cae083a8c27c..f8db835b1943ca2266fe7ed06579a9ec7fb3ba65 100644 --- a/pkg/informers/informers.go +++ b/pkg/informers/informers.go @@ -18,6 +18,7 @@ package informers import ( + applicationinformers "github.com/kubernetes-sigs/application/pkg/client/informers/externalversions" s2iinformers "github.com/kubesphere/s2ioperator/pkg/client/informers/externalversions" k8sinformers "k8s.io/client-go/informers" ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions" @@ -32,9 +33,11 @@ var ( k8sOnce sync.Once s2iOnce sync.Once ksOnce sync.Once + appOnce sync.Once informerFactory k8sinformers.SharedInformerFactory s2iInformerFactory s2iinformers.SharedInformerFactory ksInformerFactory ksinformers.SharedInformerFactory + appInformerFactory applicationinformers.SharedInformerFactory ) func SharedInformerFactory() k8sinformers.SharedInformerFactory { @@ -60,3 +63,11 @@ func KsSharedInformerFactory() ksinformers.SharedInformerFactory { }) return ksInformerFactory } + +func AppSharedInformerFactory() applicationinformers.SharedInformerFactory { + appOnce.Do(func() { + appClient := client.ClientSets().K8s().Application() + appInformerFactory = applicationinformers.NewSharedInformerFactory(appClient, defaultResync) + }) + return appInformerFactory +} diff --git a/pkg/models/resources/appplications.go b/pkg/models/resources/appplications.go new file mode 100644 index 0000000000000000000000000000000000000000..53bad8c38fcd7dd77e5d9c11eec557d3b9e70a41 --- /dev/null +++ b/pkg/models/resources/appplications.go @@ -0,0 +1,135 @@ +/* + * + * Copyright 2019 The KubeSphere Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * / + */ + +package resources + +import ( + "github.com/kubernetes-sigs/application/pkg/apis/app/v1beta1" + "k8s.io/apimachinery/pkg/labels" + "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/informers" + "kubesphere.io/kubesphere/pkg/server/params" + "kubesphere.io/kubesphere/pkg/utils/sliceutil" + "sort" + "strings" +) + +type appSearcher struct { +} + +func (*appSearcher) get(namespace, name string) (interface{}, error) { + return informers.AppSharedInformerFactory().App().V1beta1().Applications().Lister().Applications(namespace).Get(name) +} + +// exactly Match +func (*appSearcher) match(match map[string]string, item *v1beta1.Application) bool { + for k, v := range match { + switch k { + case Name: + names := strings.Split(v, "|") + if !sliceutil.HasString(names, item.Name) { + return false + } + case Keyword: + if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) { + return false + } + default: + // label not exist or value not equal + if val, ok := item.Labels[k]; !ok || val != v { + return false + } + } + } + return true +} + +// Fuzzy searchInNamespace +func (*appSearcher) fuzzy(fuzzy map[string]string, item *v1beta1.Application) bool { + for k, v := range fuzzy { + switch k { + case Name: + if !strings.Contains(item.Name, v) && !strings.Contains(item.Annotations[constants.DisplayNameAnnotationKey], v) { + return false + } + case Label: + if !searchFuzzy(item.Labels, "", v) { + return false + } + case annotation: + if !searchFuzzy(item.Annotations, "", v) { + return false + } + return false + case app: + if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) { + return false + } + default: + if !searchFuzzy(item.Labels, k, v) { + return false + } + } + } + return true +} + +func (*appSearcher) compare(a, b *v1beta1.Application, orderBy string) bool { + switch orderBy { + case CreateTime: + return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time) + case Name: + fallthrough + default: + return strings.Compare(a.Name, b.Name) <= 0 + } +} + +func (s *appSearcher) search(namespace string, conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) { + apps, err := informers.AppSharedInformerFactory().App().V1beta1().Applications().Lister().Applications(namespace).List(labels.Everything()) + + if err != nil { + return nil, err + } + + result := make([]*v1beta1.Application, 0) + + if len(conditions.Match) == 0 && len(conditions.Fuzzy) == 0 { + result = apps + } else { + for _, item := range apps { + if s.match(conditions.Match, item) && s.fuzzy(conditions.Fuzzy, item) { + result = append(result, item) + } + } + } + sort.Slice(result, func(i, j int) bool { + if reverse { + tmp := i + i = j + j = tmp + } + return s.compare(result[i], result[j], orderBy) + }) + + r := make([]interface{}, 0) + for _, i := range result { + r = append(r, i) + } + return r, nil +} diff --git a/pkg/models/resources/resources.go b/pkg/models/resources/resources.go index 05af020fb5259f8727cf05bc0caf59e9b8d598fb..46adaea912341b14d352d6b530bcb51830fc5e37 100644 --- a/pkg/models/resources/resources.go +++ b/pkg/models/resources/resources.go @@ -42,6 +42,7 @@ func init() { resources[S2iBuilders] = &s2iBuilderSearcher{} resources[S2iRuns] = &s2iRunSearcher{} resources[HorizontalPodAutoscalers] = &hpaSearcher{} + resources[Applications] = &appSearcher{} resources[Nodes] = &nodeSearcher{} resources[Namespaces] = &namespaceSearcher{} @@ -103,6 +104,7 @@ const ( Services = "services" StatefulSets = "statefulsets" HorizontalPodAutoscalers = "horizontalpodautoscalers" + Applications = "applications" Nodes = "nodes" Namespaces = "namespaces" StorageClasses = "storageclasses" diff --git a/pkg/simple/client/k8s/kubernetes.go b/pkg/simple/client/k8s/kubernetes.go index 2e83fe1ea509a7513f73d403ae52b707f90797e8..a203eb61ea751a6f34f6821734fb8d4ec5d92302 100644 --- a/pkg/simple/client/k8s/kubernetes.go +++ b/pkg/simple/client/k8s/kubernetes.go @@ -1,6 +1,7 @@ package k8s import ( + applicationclientset "github.com/kubernetes-sigs/application/pkg/client/clientset/versioned" s2i "github.com/kubesphere/s2ioperator/pkg/client/clientset/versioned" "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes" @@ -22,6 +23,8 @@ type KubernetesClient struct { s2i *s2i.Clientset + application *applicationclientset.Clientset + master string config *rest.Config @@ -42,6 +45,7 @@ func NewKubernetesClientOrDie(options *KubernetesOptions) *KubernetesClient { discoveryClient: discovery.NewDiscoveryClientForConfigOrDie(config), ks: kubesphere.NewForConfigOrDie(config), s2i: s2i.NewForConfigOrDie(config), + application: applicationclientset.NewForConfigOrDie(config), master: config.Host, config: config, } @@ -84,6 +88,11 @@ func NewKubernetesClient(options *KubernetesOptions) (*KubernetesClient, error) return nil, err } + k.application, err = applicationclientset.NewForConfig(config) + if err != nil { + return nil, err + } + k.master = options.Master k.config = config @@ -106,6 +115,10 @@ func (k *KubernetesClient) S2i() s2i.Interface { return k.s2i } +func (k *KubernetesClient) Application() applicationclientset.Interface { + return k.application +} + // master address used to generate kubeconfig for downloading func (k *KubernetesClient) Master() string { return k.master