/* * * Copyright 2020 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 openpitrix import ( "fmt" "github.com/golang/protobuf/ptypes/wrappers" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" appsv1 "k8s.io/api/apps/v1" "k8s.io/api/core/v1" "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/informers" "k8s.io/klog" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/models" "kubesphere.io/kubesphere/pkg/server/params" "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" "openpitrix.io/openpitrix/pkg/pb" "strings" ) type ApplicationInterface interface { ListApplications(conditions *params.Conditions, limit, offset int, orderBy string, reverse bool) (*models.PageableResponse, error) DescribeApplication(namespace, clusterId string) (*Application, error) CreateApplication(namespace string, request CreateClusterRequest) error ModifyApplication(request ModifyClusterAttributesRequest) error DeleteApplication(id string) error } type applicationOperator struct { informers informers.SharedInformerFactory opClient openpitrix.Client } func newApplicationOperator(informers informers.SharedInformerFactory, opClient openpitrix.Client) ApplicationInterface { return &applicationOperator{informers: informers, opClient: opClient} } type Application struct { Name string `json:"name" description:"application name"` Cluster *Cluster `json:"cluster,omitempty" description:"application cluster info"` Version *AppVersion `json:"version,omitempty" description:"application template version info"` App *App `json:"app,omitempty" description:"application template info"` WorkLoads *workLoads `json:"workloads,omitempty" description:"application workloads"` Services []v1.Service `json:"services,omitempty" description:"application services"` Ingresses []v1beta1.Ingress `json:"ingresses,omitempty" description:"application ingresses"` } type workLoads struct { Deployments []appsv1.Deployment `json:"deployments,omitempty" description:"deployment list"` Statefulsets []appsv1.StatefulSet `json:"statefulsets,omitempty" description:"statefulset list"` Daemonsets []appsv1.DaemonSet `json:"daemonsets,omitempty" description:"daemonset list"` } func (c *applicationOperator) ListApplications(conditions *params.Conditions, limit, offset int, orderBy string, reverse bool) (*models.PageableResponse, error) { describeClustersRequest := &pb.DescribeClustersRequest{ Limit: uint32(limit), Offset: uint32(offset)} if keyword := conditions.Match[Keyword]; keyword != "" { describeClustersRequest.SearchWord = &wrappers.StringValue{Value: keyword} } if runtimeId := conditions.Match[RuntimeId]; runtimeId != "" { describeClustersRequest.RuntimeId = []string{runtimeId} } if appId := conditions.Match[AppId]; appId != "" { describeClustersRequest.AppId = []string{appId} } if versionId := conditions.Match[VersionId]; versionId != "" { describeClustersRequest.VersionId = []string{versionId} } if status := conditions.Match[Status]; status != "" { describeClustersRequest.Status = strings.Split(status, "|") } if orderBy != "" { describeClustersRequest.SortKey = &wrappers.StringValue{Value: orderBy} } describeClustersRequest.Reverse = &wrappers.BoolValue{Value: reverse} resp, err := c.opClient.DescribeClusters(openpitrix.SystemContext(), describeClustersRequest) if err != nil { klog.Errorln(err) return nil, err } result := models.PageableResponse{TotalCount: int(resp.TotalCount)} result.Items = make([]interface{}, 0) for _, cluster := range resp.ClusterSet { app, err := c.describeApplication(cluster) if err != nil { klog.Errorln(err) return nil, err } result.Items = append(result.Items, app) } return &result, nil } func (c *applicationOperator) describeApplication(cluster *pb.Cluster) (*Application, error) { var app Application app.Name = cluster.Name.Value app.Cluster = convertCluster(cluster) versionInfo, err := c.opClient.DescribeAppVersions(openpitrix.SystemContext(), &pb.DescribeAppVersionsRequest{VersionId: []string{cluster.GetVersionId().GetValue()}}) if err != nil { klog.Errorln(err) return nil, err } if len(versionInfo.AppVersionSet) > 0 { app.Version = convertAppVersion(versionInfo.AppVersionSet[0]) } appInfo, err := c.opClient.DescribeApps(openpitrix.SystemContext(), &pb.DescribeAppsRequest{AppId: []string{cluster.GetAppId().GetValue()}, Limit: 1}) if err != nil { klog.Errorln(err) return nil, err } if len(appInfo.AppSet) > 0 { app.App = convertApp(appInfo.GetAppSet()[0]) } return &app, nil } func (c *applicationOperator) DescribeApplication(namespace string, clusterId string) (*Application, error) { clusters, err := c.opClient.DescribeClusters(openpitrix.SystemContext(), &pb.DescribeClustersRequest{ClusterId: []string{clusterId}, Limit: 1}) if err != nil { klog.Errorln(err) return nil, err } var cluster *pb.Cluster if len(clusters.ClusterSet) > 0 { cluster = clusters.GetClusterSet()[0] } else { err := status.New(codes.NotFound, "resource not found").Err() klog.Errorln(err) return nil, err } app, err := c.describeApplication(cluster) if err != nil { klog.Errorln(err) return nil, err } workloads, err := c.getWorkLoads(namespace, cluster.ClusterRoleSet) if err != nil { klog.Errorln(err) return nil, err } app.WorkLoads = workloads workloadLabels := c.getLabels(namespace, app.WorkLoads) app.Services = c.getSvcs(namespace, workloadLabels) app.Ingresses = c.getIng(namespace, app.Services) return app, nil } func (c *applicationOperator) getWorkLoads(namespace string, clusterRoles []*pb.ClusterRole) (*workLoads, error) { var works workLoads for _, clusterRole := range clusterRoles { workLoadName := clusterRole.Role.Value if len(workLoadName) > 0 { if strings.HasSuffix(workLoadName, openpitrix.DeploySuffix) { name := strings.Split(workLoadName, openpitrix.DeploySuffix)[0] item, err := c.informers.Apps().V1().Deployments().Lister().Deployments(namespace).Get(name) if err != nil { // app not ready if errors.IsNotFound(err) { continue } klog.Errorln(err) return nil, err } works.Deployments = append(works.Deployments, *item) continue } if strings.HasSuffix(workLoadName, openpitrix.DaemonSuffix) { name := strings.Split(workLoadName, openpitrix.DaemonSuffix)[0] item, err := c.informers.Apps().V1().DaemonSets().Lister().DaemonSets(namespace).Get(name) if err != nil { // app not ready if errors.IsNotFound(err) { continue } klog.Errorln(err) return nil, err } works.Daemonsets = append(works.Daemonsets, *item) continue } if strings.HasSuffix(workLoadName, openpitrix.StateSuffix) { name := strings.Split(workLoadName, openpitrix.StateSuffix)[0] item, err := c.informers.Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(name) if err != nil { // app not ready if errors.IsNotFound(err) { continue } klog.Errorln(err) return nil, err } works.Statefulsets = append(works.Statefulsets, *item) continue } } } return &works, nil } func (c *applicationOperator) getLabels(namespace string, workloads *workLoads) *[]map[string]string { var workloadLabels []map[string]string if workloads == nil { return nil } for _, workload := range workloads.Deployments { deploy, err := c.informers.Apps().V1().Deployments().Lister().Deployments(namespace).Get(workload.Name) if errors.IsNotFound(err) { continue } workloadLabels = append(workloadLabels, deploy.Labels) } for _, workload := range workloads.Daemonsets { daemonset, err := c.informers.Apps().V1().DaemonSets().Lister().DaemonSets(namespace).Get(workload.Name) if errors.IsNotFound(err) { continue } workloadLabels = append(workloadLabels, daemonset.Labels) } for _, workload := range workloads.Statefulsets { statefulset, err := c.informers.Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(workload.Name) if errors.IsNotFound(err) { continue } workloadLabels = append(workloadLabels, statefulset.Labels) } return &workloadLabels } func (c *applicationOperator) isExist(svcs []v1.Service, svc *v1.Service) bool { for _, item := range svcs { if item.Name == svc.Name && item.Namespace == svc.Namespace { return true } } return false } func (c *applicationOperator) getSvcs(namespace string, workLoadLabels *[]map[string]string) []v1.Service { if len(*workLoadLabels) == 0 { return nil } var services []v1.Service for _, label := range *workLoadLabels { labelSelector := labels.Set(label).AsSelector() svcs, err := c.informers.Core().V1().Services().Lister().Services(namespace).List(labelSelector) if err != nil { klog.Errorf("get app's svc failed, reason: %v", err) } for _, item := range svcs { if !c.isExist(services, item) { services = append(services, *item) } } } return services } func (c *applicationOperator) getIng(namespace string, services []v1.Service) []v1beta1.Ingress { if services == nil { return nil } var ings []v1beta1.Ingress for _, svc := range services { ingresses, err := c.informers.Extensions().V1beta1().Ingresses().Lister().Ingresses(namespace).List(labels.Everything()) if err != nil { klog.Error(err) return ings } for _, ingress := range ingresses { if ingress.Spec.Backend.ServiceName != svc.Name { continue } exist := false var tmpRules []v1beta1.IngressRule for _, rule := range ingress.Spec.Rules { for _, p := range rule.HTTP.Paths { if p.Backend.ServiceName == svc.Name { exist = true tmpRules = append(tmpRules, rule) } } } if exist { ing := v1beta1.Ingress{} ing.Name = ingress.Name ing.Spec.Rules = tmpRules ings = append(ings, ing) } } } return ings } func (c *applicationOperator) CreateApplication(namespace string, request CreateClusterRequest) error { ns, err := c.informers.Core().V1().Namespaces().Lister().Get(namespace) if err != nil { klog.Error(err) return err } if runtimeId := ns.Annotations[constants.OpenPitrixRuntimeAnnotationKey]; runtimeId != "" { request.RuntimeId = runtimeId } else { return fmt.Errorf("runtime not init: namespace %s", namespace) } _, err = c.opClient.CreateCluster(openpitrix.ContextWithUsername(request.Username), &pb.CreateClusterRequest{ AppId: &wrappers.StringValue{Value: request.AppId}, VersionId: &wrappers.StringValue{Value: request.VersionId}, RuntimeId: &wrappers.StringValue{Value: request.RuntimeId}, Conf: &wrappers.StringValue{Value: request.Conf}, }) if err != nil { klog.Errorln(err) return err } return nil } func (c *applicationOperator) ModifyApplication(request ModifyClusterAttributesRequest) error { modifyClusterAttributesRequest := &pb.ModifyClusterAttributesRequest{ClusterId: &wrappers.StringValue{Value: request.ClusterID}} if request.Name != nil { modifyClusterAttributesRequest.Name = &wrappers.StringValue{Value: *request.Name} } if request.Description != nil { modifyClusterAttributesRequest.Description = &wrappers.StringValue{Value: *request.Description} } _, err := c.opClient.ModifyClusterAttributes(openpitrix.SystemContext(), modifyClusterAttributesRequest) if err != nil { klog.Errorln(err) return err } return nil } func (c *applicationOperator) DeleteApplication(clusterId string) error { _, err := c.opClient.DeleteClusters(openpitrix.SystemContext(), &pb.DeleteClustersRequest{ClusterId: []string{clusterId}, Force: &wrappers.BoolValue{Value: true}}) if err != nil { klog.Errorln(err) return err } return nil }