├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── apidoc ├── apidoc.yaml ├── favicon-16x16.png ├── favicon-32x32.png ├── index.html ├── oauth2-redirect.html ├── swagger-ui-bundle.js ├── swagger-ui-bundle.js.map ├── swagger-ui-standalone-preset.js ├── swagger-ui-standalone-preset.js.map ├── swagger-ui.css ├── swagger-ui.css.map ├── swagger-ui.js └── swagger-ui.js.map ├── backend ├── controllermanager │ ├── controllermanager.go │ ├── register.go │ └── register │ │ ├── application.go │ │ ├── endpoint.go │ │ ├── event.go │ │ ├── harbor.go │ │ ├── ingress.go │ │ ├── namespace.go │ │ ├── node.go │ │ ├── resourcequota.go │ │ ├── secret.go │ │ └── service.go ├── controllers │ ├── application │ │ ├── appcontroller.go │ │ └── syncapp.go │ ├── endpoint │ │ ├── endpointcontroller.go │ │ ├── endpointutil.go │ │ └── syncendpoint.go │ ├── event │ │ └── eventcontroller.go │ ├── harbor │ │ └── harborcontroller.go │ ├── ingress │ │ ├── ingresscontroller.go │ │ ├── ingressutil.go │ │ └── syncingress.go │ ├── namespace │ │ └── namespace_controller.go │ ├── node │ │ └── nodecontroller.go │ ├── resourcequota │ │ └── resourcequota_controller.go │ ├── secret │ │ ├── secretcontroller.go │ │ ├── secretutil.go │ │ └── syncsecret.go │ ├── service │ │ ├── servicecontroller.go │ │ ├── serviceutil.go │ │ └── syncservice.go │ └── util │ │ └── util.go ├── dao │ ├── application.go │ ├── appversion.go │ ├── cluster.go │ ├── common.go │ ├── event.go │ ├── harbor.go │ ├── k8s_endpoint.go │ ├── k8s_ingress.go │ ├── k8s_ingress_rule.go │ ├── k8s_namespace.go │ ├── k8s_secret.go │ ├── k8s_service.go │ ├── node.go │ └── template.go ├── models │ ├── application.go │ ├── appversion.go │ ├── cluster.go │ ├── common.go │ ├── event.go │ ├── harbor.go │ ├── k8s_endpoint.go │ ├── k8s_endpoint_address.go │ ├── k8s_ingress.go │ ├── k8s_ingress_rule.go │ ├── k8s_namespace.go │ ├── k8s_secret.go │ ├── k8s_service.go │ ├── k8s_service_port.go │ ├── node.go │ └── template.go ├── resource │ ├── application.go │ ├── apptemplate.go │ ├── cluster.go │ ├── common.go │ ├── configmap.go │ ├── deployment.go │ ├── deployworker.go │ ├── event.go │ ├── exec_command.go │ ├── harbor.go │ ├── ingress.go │ ├── ingressconfig.go │ ├── kubeappres.go │ ├── namespace.go │ ├── nativeapptemplate.go │ ├── nativetemplate.go │ ├── node.go │ ├── objectvalidator.go │ ├── pod.go │ ├── secret.go │ ├── service.go │ └── template.go ├── service │ ├── appconfig.go │ ├── clienthelper.go │ ├── clientset.go │ └── service.go └── util │ ├── kubeutil │ ├── ingressutil.go │ └── serviceutil.go │ └── labels │ └── labels.go ├── cmd └── kubecloud │ └── main.go ├── common ├── const.go ├── errors.go ├── errors_test.go ├── keyword │ └── keyword.go ├── utils │ ├── nettools.go │ ├── ormfilter.go │ ├── simplelocker.go │ ├── synclocker.go │ ├── utils.go │ ├── utils_test.go │ ├── visgraph.go │ └── vizgraph.go └── validate │ └── validate.go ├── conf └── app.conf ├── controllers ├── application.go ├── base.go ├── clusters.go ├── common.go ├── configmap.go ├── error.go ├── event.go ├── harbor.go ├── ingress.go ├── namespaces.go ├── node.go ├── secret.go ├── services.go ├── terminal.go └── version.go ├── deploy └── kubecloud.yaml ├── docs └── images │ └── kubecloud-architecture.png ├── gitops ├── git.go └── gitops.go ├── go.mod ├── go.sum └── routers └── router.go /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX leaves these everywhere on SMB shares 2 | ._* 3 | 4 | # OSX trash 5 | .DS_Store 6 | 7 | # Eclipse files 8 | .classpath 9 | .project 10 | .settings/** 11 | 12 | # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA 13 | .idea/ 14 | *.iml 15 | 16 | # Vscode files 17 | .vscode 18 | 19 | # Emacs save files 20 | *~ 21 | \#*\# 22 | .\#* 23 | 24 | # Vim-related files 25 | [._]*.s[a-w][a-z] 26 | [._]s[a-w][a-z] 27 | *.un~ 28 | Session.vim 29 | .netrwhist 30 | 31 | # Go test binaries 32 | *.test 33 | 34 | *.exe 35 | *.log 36 | *.swp 37 | /conf/app.local.conf 38 | /conf/k8sconfig 39 | /kubecloud 40 | /configRepo/ 41 | /k8sconfig/ 42 | /vendor/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12.9-alpine as base 2 | RUN apk --update upgrade 3 | RUN apk --no-cache add tzdata make bash curl g++ git 4 | RUN rm -rf /var/cache/apk/* 5 | RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 6 | RUN echo "Asia/Shanghai" > /etc/timezone 7 | 8 | FROM base as builder 9 | WORKDIR $GOPATH/src/kubecloud 10 | COPY . . 11 | RUN CGO_ENABLED=1 INSTALL_DIR=/kubecloud make install clean 12 | 13 | FROM base 14 | WORKDIR /kubecloud 15 | COPY --from=builder /kubecloud . 16 | EXPOSE 8080 17 | CMD ["./kubecloud"] 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build 2 | 3 | INSTALL_DIR?=./kubecloud 4 | GO_TAGS?= 5 | CGO_ENABLED?=0 6 | GO_PACKAGES?=./... 7 | 8 | GO_BUILD=go build -v -tags="${GO_TAGS}" 9 | 10 | export CGO_ENABLED := ${CGO_ENABLED} 11 | 12 | all: build 13 | 14 | build: 15 | ${GO_BUILD} ./cmd/kubecloud 16 | 17 | lint: 18 | go vet -structtag=false -tags="${GO_TAGS}" ${GO_PACKAGES} 19 | 20 | # NOTE: cgo is required by go test 21 | test: 22 | CGO_ENABLED=1 go test -race -cover -failfast -vet=off -tags="${GO_TAGS}" ${GO_PACKAGES} 23 | 24 | install: build 25 | mkdir -p ${INSTALL_DIR} 26 | mkdir -p ${INSTALL_DIR}/log 27 | cp -Rp kubecloud conf apidoc ${INSTALL_DIR} 28 | 29 | docker: 30 | docker build -t kubecloud . 31 | 32 | clean: 33 | rm -rf cli web 34 | go clean -cache -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubecloud 2 | 3 | Kubecloud是一个多云多集群容器云管理平台,他可以帮助用户方便的部署和管理容器,以及搭建在公有云、私有云或自建IDC环境的K8s集群。 4 | Kubecloud是以Gitops的方式管理各个Kubernetes集群声明式资源的yaml配置将其存放在git配置仓库中,提供安全可靠的基础设施服务。 5 | 同时Kubecloud也积极跟随云原生社区发展,后续将不断完善产品功能并向ServiseMesh、Serverless、边缘计算等方向探索。 6 | 7 | Kubecloud的主要功能以及特性: 8 | 9 | - 提供API服务管理k8s集群以及资源编排 10 | - 方便易用的可视化界面 11 | - 多云环境的k8s集群及主机、网络、存储、容器等资源管理 12 | - 基于Harbor的镜像仓库管理功能 13 | - 使用Gitops管理k8s集群声明式基础设施的yaml配置文件 14 | - 应用市场功能方便快速部署海量企业级应用 15 | - 支持基于Istio的Servise Mesh功能 16 | - 支持基于Knative的Serverless功能 17 | - 支持机器学习训练及模型部署 18 | - 支持基于KubeEdge的容器化的边缘计算平台 19 | 20 | ## 架构概述 21 | ![](docs/images/kubecloud-architecture.png) 22 | 23 | 24 | ## 部署Kubecloud 25 | 26 | ### 依赖条件 27 | 28 | 1. Mysql 5.7版本或以上 29 | 2. Harbor 1.7.5版本或以上 30 | 3. K8s集群 1.12版本或以上 31 | 4. Git配置仓库,例如gitlab,github等 32 | 5. Seaman 0.1.0版本 33 | 34 | 35 | ### 使用kubernets部署 36 | 37 | $ kubectl apply -f deploy/kubecloud.yaml 38 | 39 | 40 | ### 使用Docker镜像部署 41 | 42 | 你可以使用以下命令启动kubecloud: 43 | 44 | $ mkdir -p $GOPATH/src/github.com/kubecloud 45 | $ cd $GOPATH/src/github.com/kubecloud 46 | $ git clone https://github.com/ZhongAnTech/kubecloud.git 47 | $ cd kubecloud 48 | $ make docker 49 | $ docker run --name kubecloud -d -p 8080:8080 kubecloud 50 | 51 | 52 | ### 编译源码 53 | 54 | $ mkdir -p $GOPATH/src/github.com/kubecloud 55 | $ cd $GOPATH/src/github.com/kubecloud 56 | $ git clone https://github.com/ZhongAnTech/kubecloud.git 57 | $ cd kubecloud 58 | $ make build 59 | $ ./kubecloud 60 | 61 | ## API文档 62 | 63 | http://localhost:8080/apidoc/idex.html 64 | 65 | 66 | 67 | ## License 68 | 69 | Apache License 2.0, see [LICENSE](LICENSE). 70 | -------------------------------------------------------------------------------- /apidoc/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhongAnTech/kubecloud/0fc068f5f55a7d10935dcecf52e0813b1a4fa6a3/apidoc/favicon-16x16.png -------------------------------------------------------------------------------- /apidoc/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhongAnTech/kubecloud/0fc068f5f55a7d10935dcecf52e0813b1a4fa6a3/apidoc/favicon-32x32.png -------------------------------------------------------------------------------- /apidoc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | 71 | 72 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /apidoc/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 54 | -------------------------------------------------------------------------------- /apidoc/swagger-ui-bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAsyKA;;;;;;AAktEA;;;;;;;;;;;;;;;;;;;;;;;;;;AAkqTA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AA4tnBA;;;;;AAmpQA;;;;;;AA+mXA","sourceRoot":""} -------------------------------------------------------------------------------- /apidoc/swagger-ui-standalone-preset.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA40CA;;;;;;AAqlFA","sourceRoot":""} -------------------------------------------------------------------------------- /apidoc/swagger-ui.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"swagger-ui.css","sources":[],"mappings":";;;","sourceRoot":""} -------------------------------------------------------------------------------- /apidoc/swagger-ui.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAk/aA","sourceRoot":""} -------------------------------------------------------------------------------- /backend/controllermanager/register.go: -------------------------------------------------------------------------------- 1 | package controllermanager 2 | 3 | type StartController func(ctx ControllerContext) error 4 | 5 | var controllerList map[string]StartController 6 | 7 | func RegisterController(name string, startFunc StartController) { 8 | if controllerList == nil { 9 | controllerList = make(map[string]StartController) 10 | } 11 | controllerList[name] = startFunc 12 | } 13 | 14 | func GetControllerList() map[string]StartController { 15 | return controllerList 16 | } 17 | -------------------------------------------------------------------------------- /backend/controllermanager/register/application.go: -------------------------------------------------------------------------------- 1 | package register 2 | 3 | import ( 4 | "fmt" 5 | cm "kubecloud/backend/controllermanager" 6 | "kubecloud/backend/controllers/application" 7 | ) 8 | 9 | func startApplicationController(ctx cm.ControllerContext) error { 10 | ac, err := application.NewApplicationController( 11 | ctx.Cluster, ctx.Client, 12 | ctx.InformerFactory.Apps().V1beta1().Deployments()) 13 | if err != nil { 14 | return fmt.Errorf("error creating deployment controller: %v", err) 15 | } 16 | go ac.Run(ctx.Option.NormalConcurrentSyncs, ctx.Stop) 17 | return nil 18 | } 19 | 20 | func init() { 21 | cm.RegisterController("application", startApplicationController) 22 | } 23 | -------------------------------------------------------------------------------- /backend/controllermanager/register/endpoint.go: -------------------------------------------------------------------------------- 1 | package register 2 | 3 | import ( 4 | "fmt" 5 | cm "kubecloud/backend/controllermanager" 6 | "kubecloud/backend/controllers/endpoint" 7 | ) 8 | 9 | func startEndpointController(ctx cm.ControllerContext) error { 10 | ac, err := endpoint.NewEndpointController( 11 | ctx.Cluster, ctx.Client, 12 | ctx.InformerFactory.Core().V1().Endpoints()) 13 | if err != nil { 14 | return fmt.Errorf("error creating service controller: %v", err) 15 | } 16 | go ac.Run(ctx.Option.NormalConcurrentSyncs, ctx.Stop) 17 | return nil 18 | } 19 | 20 | func init() { 21 | cm.RegisterController("endpoint", startEndpointController) 22 | } 23 | -------------------------------------------------------------------------------- /backend/controllermanager/register/event.go: -------------------------------------------------------------------------------- 1 | package register 2 | 3 | import ( 4 | cm "kubecloud/backend/controllermanager" 5 | "kubecloud/backend/controllers/event" 6 | ) 7 | 8 | func startEventController(ctx cm.ControllerContext) error { 9 | ec := event.NewEventController( 10 | ctx.Cluster, 11 | ctx.InformerFactory.Core().V1().Events(), 12 | ctx.Client) 13 | 14 | go ec.Run(1, ctx.Stop) 15 | return nil 16 | } 17 | 18 | func init() { 19 | cm.RegisterController("event", startEventController) 20 | } 21 | -------------------------------------------------------------------------------- /backend/controllermanager/register/harbor.go: -------------------------------------------------------------------------------- 1 | package register 2 | 3 | import ( 4 | cm "kubecloud/backend/controllermanager" 5 | "kubecloud/backend/controllers/harbor" 6 | ) 7 | 8 | func startHarborController(ctx cm.ControllerContext) error { 9 | hc, err := harbor.NewHarborController(ctx.Cluster) 10 | if err != nil { 11 | return err 12 | } 13 | go hc.Run(ctx.Stop) 14 | return nil 15 | } 16 | 17 | func init() { 18 | cm.RegisterController("harbor", startHarborController) 19 | } 20 | -------------------------------------------------------------------------------- /backend/controllermanager/register/ingress.go: -------------------------------------------------------------------------------- 1 | package register 2 | 3 | import ( 4 | "fmt" 5 | cm "kubecloud/backend/controllermanager" 6 | "kubecloud/backend/controllers/ingress" 7 | ) 8 | 9 | func startIngressController(ctx cm.ControllerContext) error { 10 | ic, err := ingress.NewIngressController( 11 | ctx.Cluster, ctx.Client, 12 | ctx.InformerFactory.Extensions().V1beta1().Ingresses()) 13 | if err != nil { 14 | return fmt.Errorf("error creating ingress controller: %v", err) 15 | } 16 | go ic.Run(ctx.Option.NormalConcurrentSyncs, ctx.Stop) 17 | return nil 18 | } 19 | 20 | func init() { 21 | cm.RegisterController("ingress", startIngressController) 22 | } 23 | -------------------------------------------------------------------------------- /backend/controllermanager/register/namespace.go: -------------------------------------------------------------------------------- 1 | package register 2 | 3 | import ( 4 | cm "kubecloud/backend/controllermanager" 5 | "kubecloud/backend/controllers/namespace" 6 | ) 7 | 8 | func startNamespaceController(ctx cm.ControllerContext) error { 9 | controller := namespace.NewNamespaceController( 10 | ctx.Cluster, ctx.Client, 11 | ctx.InformerFactory.Core().V1().Namespaces()) 12 | go controller.Run(ctx.Option.NormalConcurrentSyncs, ctx.Stop) 13 | return nil 14 | } 15 | 16 | func init() { 17 | //cm.RegisterController("namespace", startNamespaceController) 18 | } 19 | -------------------------------------------------------------------------------- /backend/controllermanager/register/node.go: -------------------------------------------------------------------------------- 1 | package register 2 | 3 | import ( 4 | cm "kubecloud/backend/controllermanager" 5 | "kubecloud/backend/controllers/node" 6 | ) 7 | 8 | func startNodeController(ctx cm.ControllerContext) error { 9 | nc, err := node.NewNodeController( 10 | ctx.Cluster, 11 | ctx.InformerFactory.Core().V1().Nodes(), 12 | ctx.Client) 13 | if err != nil { 14 | return err 15 | } 16 | 17 | go nc.Run(1, ctx.Stop) 18 | return nil 19 | } 20 | 21 | func init() { 22 | cm.RegisterController("node", startNodeController) 23 | } 24 | -------------------------------------------------------------------------------- /backend/controllermanager/register/resourcequota.go: -------------------------------------------------------------------------------- 1 | package register 2 | 3 | import ( 4 | cm "kubecloud/backend/controllermanager" 5 | "kubecloud/backend/controllers/resourcequota" 6 | ) 7 | 8 | func startResourceQuotaController(ctx cm.ControllerContext) error { 9 | controller := resourcequota.NewResourceQuotaController( 10 | ctx.Cluster, ctx.Client, 11 | ctx.InformerFactory.Core().V1().ResourceQuotas()) 12 | go controller.Run(ctx.Option.NormalConcurrentSyncs, ctx.Stop) 13 | return nil 14 | } 15 | 16 | func init() { 17 | //cm.RegisterController("resourcequota", startResourceQuotaController) 18 | } 19 | -------------------------------------------------------------------------------- /backend/controllermanager/register/secret.go: -------------------------------------------------------------------------------- 1 | package register 2 | 3 | import ( 4 | "fmt" 5 | cm "kubecloud/backend/controllermanager" 6 | "kubecloud/backend/controllers/secret" 7 | ) 8 | 9 | func startSecretController(ctx cm.ControllerContext) error { 10 | sc, err := secret.NewSecretController( 11 | ctx.Cluster, ctx.Client, 12 | ctx.InformerFactory.Core().V1().Secrets()) 13 | if err != nil { 14 | return fmt.Errorf("error creating secret controller: %v", err) 15 | } 16 | go sc.Run(ctx.Option.NormalConcurrentSyncs, ctx.Stop) 17 | return nil 18 | } 19 | 20 | func init() { 21 | cm.RegisterController("secret", startSecretController) 22 | } 23 | -------------------------------------------------------------------------------- /backend/controllermanager/register/service.go: -------------------------------------------------------------------------------- 1 | package register 2 | 3 | import ( 4 | "fmt" 5 | cm "kubecloud/backend/controllermanager" 6 | "kubecloud/backend/controllers/service" 7 | ) 8 | 9 | func startServiceController(ctx cm.ControllerContext) error { 10 | ac, err := service.NewServiceController( 11 | ctx.Cluster, ctx.Client, 12 | ctx.InformerFactory.Core().V1().Services()) 13 | if err != nil { 14 | return fmt.Errorf("error creating service controller: %v", err) 15 | } 16 | go ac.Run(ctx.Option.NormalConcurrentSyncs, ctx.Stop) 17 | return nil 18 | } 19 | 20 | func init() { 21 | cm.RegisterController("service", startServiceController) 22 | } 23 | -------------------------------------------------------------------------------- /backend/controllers/application/syncapp.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/astaxie/beego" 8 | v1beta1 "k8s.io/api/apps/v1beta1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | 11 | "kubecloud/backend/controllers/util" 12 | "kubecloud/backend/dao" 13 | "kubecloud/backend/resource" 14 | "kubecloud/common/keyword" 15 | ) 16 | 17 | type appStatus struct { 18 | ReadyReplicas int32 19 | AvailableReplicas int32 20 | AvailableStatus string 21 | Message string 22 | } 23 | 24 | type syncApplication struct { 25 | appHandler *dao.AppModel 26 | cluster string 27 | } 28 | 29 | func newSyncApplication(cluster string) *syncApplication { 30 | return &syncApplication{ 31 | appHandler: dao.NewAppModel(), 32 | cluster: cluster, 33 | } 34 | } 35 | 36 | // update if the app is existed, or add it 37 | func (sa *syncApplication) syncDeployApplication(deployment v1beta1.Deployment) error { 38 | appname := getAppNameByDeploy(deployment) 39 | if !sa.appHandler.AppExist(sa.cluster, deployment.Namespace, appname) { 40 | return fmt.Errorf("application(%s/%s/%s) is not existed in db, the deployment is %s!", sa.cluster, deployment.Namespace, appname, deployment.Name) 41 | } else { 42 | return sa.updateDeployStatus(appname, deployment) 43 | } 44 | } 45 | 46 | func (sa *syncApplication) updateDeployStatus(appname string, deployment v1beta1.Deployment) error { 47 | app, err := sa.appHandler.GetAppByName(sa.cluster, deployment.Namespace, appname) 48 | if err != nil { 49 | return err 50 | } 51 | if deployment.Labels["heritage"] != "Tiller" { 52 | version := resource.GetResourceVersion(&deployment, resource.ResTypeDeploy, app.Image) 53 | if resource.GetResourceVersion(app, resource.ResTypeApp, "") != version { 54 | beego.Warn(fmt.Sprintf("application(%s/%s/%s) dont need update for versions(%s/%s) are not equal!", app.Cluster, app.Namespace, app.Name, version, resource.GetResourceVersion(app, resource.ResTypeApp, ""))) 55 | return nil 56 | } 57 | } 58 | 59 | needUpdate := false 60 | smFlag := util.GetAnnotationStringValue(resource.InjectSidecarAnnotationKey, deployment.Annotations, "") 61 | 62 | if deployment.Labels["heritage"] == "Tiller" { 63 | deployment.TypeMeta = metav1.TypeMeta{ 64 | Kind: "Deployment", 65 | APIVersion: "apps/v1beta1", 66 | } 67 | deployment.ObjectMeta.ResourceVersion = "" 68 | deployment.ObjectMeta.SelfLink = "" 69 | deployment.ObjectMeta.UID = "" 70 | deployment.ObjectMeta.Generation = 0 71 | deployment.ObjectMeta.CreationTimestamp = metav1.Time{} 72 | nativeTemplate := resource.NativeAppTemplate{ 73 | TypeMeta: deployment.TypeMeta, 74 | ObjectMeta: deployment.ObjectMeta, 75 | Deployment: &deployment, 76 | } 77 | jsonData, err := json.Marshal(nativeTemplate) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | app.Template = string(jsonData) 83 | selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ 84 | MatchLabels: deployment.Spec.Selector.MatchLabels, 85 | }) 86 | if err != nil { 87 | return err 88 | } 89 | app.LabelSelector = selector.String() 90 | needUpdate = true 91 | } 92 | 93 | // because app.InjectServiceMesh "" is equal with "false" 94 | if (app.InjectServiceMesh == "" || app.InjectServiceMesh == "false") && smFlag == "true" || 95 | (smFlag == "false" && app.InjectServiceMesh == "true") { 96 | app.InjectServiceMesh = smFlag 97 | needUpdate = true 98 | } 99 | if app.Replicas != int(*deployment.Spec.Replicas) { 100 | app.Replicas = int(*deployment.Spec.Replicas) 101 | template, err := resource.CreateAppTemplateByApp(*app) 102 | if err != nil { 103 | beego.Error("create app template by app failed:", err) 104 | } else { 105 | //synchronize information 106 | if tstr, err := template.Replicas(app.Replicas).String(); err == nil { 107 | app.Template = tstr 108 | needUpdate = true 109 | } else { 110 | beego.Error("template switch to string failed:", err) 111 | } 112 | } 113 | } 114 | if app.StatusReplicas != deployment.Status.Replicas { 115 | app.StatusReplicas = deployment.Status.Replicas 116 | needUpdate = true 117 | } 118 | if app.ReadyReplicas != deployment.Status.ReadyReplicas { 119 | app.ReadyReplicas = deployment.Status.ReadyReplicas 120 | needUpdate = true 121 | } 122 | if app.AvailableReplicas != deployment.Status.AvailableReplicas { 123 | app.AvailableReplicas = deployment.Status.AvailableReplicas 124 | needUpdate = true 125 | } 126 | if app.UpdatedReplicas != deployment.Status.UpdatedReplicas { 127 | app.UpdatedReplicas = deployment.Status.UpdatedReplicas 128 | needUpdate = true 129 | } 130 | for _, condition := range deployment.Status.Conditions { 131 | if condition.Type == v1beta1.DeploymentAvailable { 132 | if string(condition.Status) != app.AvailableStatus { 133 | app.AvailableStatus = string(condition.Status) 134 | needUpdate = true 135 | } 136 | if condition.Message != app.Message { 137 | app.Message = condition.Message 138 | needUpdate = true 139 | } 140 | break 141 | } 142 | } 143 | if podv, ok := deployment.Labels[keyword.LABEL_PODVERSION_KEY]; !ok || podv == "" { 144 | app.PodVersion = "" 145 | needUpdate = true 146 | } 147 | if needUpdate { 148 | err = sa.appHandler.UpdateApp(app, false) 149 | if err != nil { 150 | beego.Error("Update application", sa.cluster, app.Namespace, app.Name, "failed for", err) 151 | } else { 152 | beego.Info("Update application", sa.cluster, app.Namespace, app.Name, "successfully") 153 | } 154 | return err 155 | } 156 | return nil 157 | } 158 | 159 | func getAppNameByDeploy(deploy v1beta1.Deployment) string { 160 | appname := deploy.Name 161 | if deploy.Labels["heritage"] != "Tiller" { 162 | if v, ok := deploy.Labels[keyword.LABEL_APPNAME_KEY]; ok { 163 | appname = v 164 | } 165 | } 166 | return appname 167 | } 168 | -------------------------------------------------------------------------------- /backend/controllers/endpoint/endpointutil.go: -------------------------------------------------------------------------------- 1 | package endpoint 2 | 3 | import ( 4 | "kubecloud/backend/controllers/util" 5 | "kubecloud/backend/models" 6 | "kubecloud/backend/resource" 7 | 8 | core "k8s.io/api/core/v1" 9 | ) 10 | 11 | //generate endpoint template by service. return endpoint struct 12 | func genEndpointRecord(cluster string, endpoint core.Endpoints, subnetIndex, portIndex int) models.K8sEndpoint { 13 | record := models.K8sEndpoint{ 14 | Cluster: cluster, 15 | Namespace: endpoint.Namespace, 16 | Name: endpoint.Name, 17 | } 18 | record.OwnerName = util.GetAnnotationStringValue(resource.OwnerNameAnnotationKey, 19 | endpoint.Annotations, resource.GetApplicationNameBySvcName(endpoint.Name)) 20 | port := endpoint.Subsets[subnetIndex].Ports[portIndex] 21 | record.Port = port.Port 22 | record.PortName = port.Name 23 | record.Protocol = string(port.Protocol) 24 | // generate address 25 | for _, item := range endpoint.Subsets[subnetIndex].Addresses { 26 | address := models.K8sEndpointAddress{ 27 | Cluster: cluster, 28 | Namespace: endpoint.Namespace, 29 | EndpointName: endpoint.Name, 30 | IP: item.IP, 31 | } 32 | if item.NodeName != nil { 33 | address.NodeName = *item.NodeName 34 | } 35 | if item.TargetRef != nil { 36 | address.TargetRefName = item.TargetRef.Name 37 | } 38 | record.Addresses = append(record.Addresses, &address) 39 | } 40 | 41 | return record 42 | } 43 | 44 | func serviceIsEqual(os, ns models.K8sService) bool { 45 | equal := true 46 | if os.ClusterIP != ns.ClusterIP || 47 | os.Type != ns.Type || 48 | len(os.Ports) != len(ns.Ports) { 49 | equal = false 50 | } 51 | for _, np := range ns.Ports { 52 | if !equal { 53 | break 54 | } 55 | exist := false 56 | for _, op := range os.Ports { 57 | if np.Name == op.Name { 58 | exist = true 59 | if np.Port != op.Port || 60 | np.TargetPort != op.TargetPort || 61 | np.Protocol != op.Protocol || 62 | np.NodePort != op.NodePort { 63 | equal = false 64 | } 65 | break 66 | } 67 | } 68 | if !exist { 69 | equal = false 70 | } 71 | } 72 | return equal 73 | } 74 | -------------------------------------------------------------------------------- /backend/controllers/endpoint/syncendpoint.go: -------------------------------------------------------------------------------- 1 | package endpoint 2 | 3 | import ( 4 | "kubecloud/backend/models" 5 | 6 | "fmt" 7 | "github.com/astaxie/beego" 8 | "github.com/astaxie/beego/orm" 9 | core "k8s.io/api/core/v1" 10 | ) 11 | 12 | // update if the endpoint is existed, or add it 13 | func (ec *EndpointController) syncEndpointRecord(endpoint core.Endpoints) error { 14 | if len(endpoint.Subsets) == 0 { 15 | return nil 16 | } 17 | for index, port := range endpoint.Subsets[0].Ports { 18 | old, err := ec.endpointHandler.Get(ec.cluster, endpoint.Namespace, 19 | endpoint.Name, port.Port) 20 | if err != nil { 21 | if err != orm.ErrNoRows { 22 | return err 23 | } 24 | err = ec.createEndpointRecord(endpoint, 0, index) 25 | } else { 26 | err = ec.updateEndpointRecord(endpoint, *old, 0, index) 27 | } 28 | if err != nil { 29 | return err 30 | } 31 | } 32 | // delete some ash port 33 | list, err := ec.endpointHandler.ListByName(ec.cluster, endpoint.Namespace, endpoint.Name) 34 | if err != nil { 35 | return err 36 | } 37 | for _, item := range list { 38 | found := false 39 | for _, port := range endpoint.Subsets[0].Ports { 40 | if item.Port == port.Port { 41 | found = true 42 | break 43 | } 44 | } 45 | if !found { 46 | ec.endpointHandler.DeleteByID(item.Id) 47 | } 48 | } 49 | return nil 50 | } 51 | 52 | func (ec *EndpointController) deleteEndpointRecord(namespace, name string) error { 53 | // delete service 54 | err := ec.endpointHandler.Delete(ec.cluster, namespace, name) 55 | if err != nil { 56 | beego.Error("Delete kube service record", ec.cluster, namespace, name, "failed for", err) 57 | } 58 | return err 59 | } 60 | 61 | func (ec *EndpointController) createEndpointRecord(endpoint core.Endpoints, subnetIndex, portIndex int) error { 62 | record := genEndpointRecord(ec.cluster, endpoint, subnetIndex, portIndex) 63 | if len(record.Addresses) == 0 { 64 | return fmt.Errorf("Create kube endpoint(%s/%s/%s) record failed: has no endpoint addresses!", ec.cluster, record.Namespace, record.Name) 65 | } 66 | err := ec.endpointHandler.Create(record) 67 | if err != nil { 68 | beego.Error("Create kube endpoint record", ec.cluster, record.Namespace, record.Name, "failed for", err) 69 | return err 70 | } 71 | return nil 72 | } 73 | 74 | func (ec *EndpointController) updateEndpointRecord(endpoint core.Endpoints, old models.K8sEndpoint, subnetIndex, portIndex int) error { 75 | record := genEndpointRecord(ec.cluster, endpoint, subnetIndex, portIndex) 76 | err := ec.endpointHandler.Update(old, record) 77 | if err != nil { 78 | beego.Error("Update kube endpoint", ec.cluster, record.Namespace, record.Name, "failed for", err) 79 | return err 80 | } 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /backend/controllers/harbor/harborcontroller.go: -------------------------------------------------------------------------------- 1 | package harbor 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "kubecloud/backend/resource" 8 | 9 | "github.com/astaxie/beego" 10 | "k8s.io/apimachinery/pkg/util/wait" 11 | "kubecloud/backend/dao" 12 | "kubecloud/backend/models" 13 | ) 14 | 15 | // harbor controller is a global controller in system 16 | type HarborController struct { 17 | cluster *models.ZcloudCluster 18 | harbor *models.ZcloudHarbor 19 | handler func() error 20 | } 21 | 22 | var clusterHarbor map[string]interface{} 23 | 24 | func harborControllerIsRunning(harborAddr string) bool { 25 | _, ok := clusterHarbor[harborAddr] 26 | return ok 27 | } 28 | 29 | // NewHarborController creates a new HarborController. 30 | func NewHarborController(cluster string) (*HarborController, error) { 31 | info, err := dao.GetCluster(cluster) 32 | if err != nil { 33 | return nil, err 34 | } 35 | harbor, err := dao.GetHarbor(info.Registry) 36 | if err != nil { 37 | return nil, err 38 | } 39 | if harborControllerIsRunning(harbor.HarborAddr) { 40 | return nil, fmt.Errorf("harbor controller of this harbor is running!") 41 | } 42 | if clusterHarbor == nil { 43 | clusterHarbor = make(map[string]interface{}) 44 | } 45 | clusterHarbor[harbor.HarborAddr] = nil 46 | hc := &HarborController{cluster: info, harbor: harbor} 47 | 48 | hc.handler = hc.syncHarbor 49 | 50 | return hc, nil 51 | } 52 | 53 | // Run begins watching and syncing. 54 | func (hc *HarborController) Run(stopCh <-chan struct{}) { 55 | syncTime, err := beego.AppConfig.Int64("harbor::syncTime") 56 | if syncTime == 0 || err != nil { 57 | syncTime = 60 58 | } 59 | go wait.Until(hc.worker, time.Duration(syncTime)*time.Second, stopCh) 60 | <-stopCh 61 | } 62 | 63 | // worker runs a worker thread that just dequeues items, processes them, and marks them done. 64 | // It enforces that the syncHandler is never invoked concurrently with the same key. 65 | func (hc *HarborController) worker() { 66 | beego.Debug("start synchronizing harbor infomation for cluster " + hc.cluster.Name + "...") 67 | hc.processNextWorkItem() 68 | beego.Debug("finish synchronizing harbor infomation for cluster " + hc.cluster.Name + "!") 69 | } 70 | 71 | func (hc *HarborController) processNextWorkItem() bool { 72 | if err := hc.handler(); err != nil { 73 | beego.Warn("sync harbor failed:", err) 74 | return false 75 | } 76 | return true 77 | } 78 | 79 | func (hc *HarborController) syncHarbor() error { 80 | startTime := time.Now() 81 | defer func() { 82 | beego.Info(fmt.Sprintf("Finished syncing harbor %s/%q (%v)", hc.harbor.HarborId, hc.harbor.HarborName, time.Now().Sub(startTime))) 83 | }() 84 | 85 | return resource.SyncHarborRepositoryData(hc.harbor) 86 | } 87 | -------------------------------------------------------------------------------- /backend/controllers/ingress/ingresscontroller.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "kubecloud/backend/controllers/util" 8 | dao "kubecloud/backend/dao" 9 | 10 | "github.com/astaxie/beego" 11 | 12 | extensions "k8s.io/api/extensions/v1beta1" 13 | "k8s.io/apimachinery/pkg/api/errors" 14 | "k8s.io/apimachinery/pkg/util/wait" 15 | extensionsinformers "k8s.io/client-go/informers/extensions/v1beta1" 16 | "k8s.io/client-go/kubernetes" 17 | extensionslisters "k8s.io/client-go/listers/extensions/v1beta1" 18 | "k8s.io/client-go/tools/cache" 19 | "k8s.io/client-go/util/workqueue" 20 | ) 21 | 22 | const ( 23 | // maxRetries is the number of times a ingress will be retried before it is dropped out of the queue. 24 | // With the current rate-limiter in use (5ms*2^(maxRetries-1)) the following numbers represent the times 25 | // a ingress is going to be requeued: 26 | // 27 | // 5ms, 10ms, 20ms, 40ms, 80ms, 160ms, 320ms, 640ms, 1.3s, 2.6s, 5.1s, 10.2s, 20.4s, 41s, 82s 28 | maxRetries = 10 29 | ) 30 | 31 | // IngressController is responsible for synchronizing ingress objects stored 32 | type IngressController struct { 33 | cluster string 34 | client kubernetes.Interface 35 | 36 | // To allow injection of syncIngress for testing. 37 | syncHandler func(dKey string) error 38 | // used for unit testing 39 | enqueueIngress func(ing *extensions.Ingress) 40 | 41 | // ingLister can list/get ingresses from the shared informer's store 42 | ingLister extensionslisters.IngressLister 43 | 44 | // ingListerSynced returns true if the ingress store has been synced at least once. 45 | // Added as a member to the struct to allow injection for testing. 46 | ingListerSynced cache.InformerSynced 47 | 48 | // Ingresses that need to be synced 49 | queue workqueue.RateLimitingInterface 50 | // ingress dbhandler 51 | kubeIngHandler *dao.K8sIngressModel 52 | } 53 | 54 | // NewIngressController creates a new IngressController. 55 | func NewIngressController(cluster string, 56 | client kubernetes.Interface, 57 | ingInformer extensionsinformers.IngressInformer) (*IngressController, error) { 58 | ic := &IngressController{ 59 | cluster: cluster, 60 | client: client, 61 | queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ingress"), 62 | } 63 | 64 | ingInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 65 | AddFunc: ic.addIngress, 66 | UpdateFunc: ic.updateIngress, 67 | DeleteFunc: ic.deleteIngress, 68 | }) 69 | ic.syncHandler = ic.syncIngress 70 | ic.enqueueIngress = ic.enqueue 71 | 72 | ic.ingLister = ingInformer.Lister() 73 | ic.ingListerSynced = ingInformer.Informer().HasSynced 74 | 75 | ic.kubeIngHandler = dao.NewK8sIngressModel() 76 | 77 | return ic, nil 78 | } 79 | 80 | // Run begins watching and syncing. 81 | func (ic *IngressController) Run(workers int, stopCh <-chan struct{}) { 82 | defer ic.queue.ShutDown() 83 | 84 | if !cache.WaitForCacheSync(stopCh, ic.ingListerSynced) { 85 | beego.Error("ingress controller cache sync failed!") 86 | return 87 | } 88 | 89 | for i := 0; i < workers; i++ { 90 | go wait.Until(ic.worker, time.Second, stopCh) 91 | } 92 | 93 | <-stopCh 94 | } 95 | 96 | func (ic *IngressController) addIngress(obj interface{}) { 97 | ing := obj.(*extensions.Ingress) 98 | ic.enqueueIngress(ing) 99 | } 100 | 101 | func (ic *IngressController) updateIngress(old, cur interface{}) { 102 | //olding := old.(*extensions.Ingress) 103 | curing := cur.(*extensions.Ingress) 104 | ic.enqueueIngress(curing) 105 | } 106 | 107 | func (ic *IngressController) deleteIngress(obj interface{}) { 108 | ing, ok := obj.(*extensions.Ingress) 109 | if !ok { 110 | tombstone, ok := obj.(cache.DeletedFinalStateUnknown) 111 | if !ok { 112 | beego.Error(fmt.Errorf("Couldn't get object from tombstone %#v", obj)) 113 | return 114 | } 115 | ing, ok = tombstone.Obj.(*extensions.Ingress) 116 | if !ok { 117 | beego.Error(fmt.Errorf("Tombstone contained object that is not a Ingress %#v", obj)) 118 | return 119 | } 120 | } 121 | ic.enqueueIngress(ing) 122 | } 123 | 124 | func (ic *IngressController) enqueue(ing *extensions.Ingress) { 125 | key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(ing) 126 | if err != nil { 127 | beego.Error(fmt.Errorf("Couldn't get key for object %#v: %v", ing, err)) 128 | return 129 | } 130 | 131 | ic.queue.Add(key) 132 | } 133 | 134 | // worker runs a worker thread that just dequeues items, processes them, and marks them done. 135 | // It enforces that the syncHandler is never invoked concurrently with the same key. 136 | func (ic *IngressController) worker() { 137 | for ic.processNextWorkItem() { 138 | } 139 | } 140 | 141 | func (ic *IngressController) processNextWorkItem() bool { 142 | key, quit := ic.queue.Get() 143 | if quit { 144 | beego.Error("get item from workqueue failed!") 145 | return false 146 | } 147 | defer ic.queue.Done(key) 148 | 149 | err := ic.syncHandler(key.(string)) 150 | ic.handleErr(err, key) 151 | 152 | return true 153 | } 154 | 155 | func (ic *IngressController) handleErr(err error, key interface{}) { 156 | if err == nil { 157 | ic.queue.Forget(key) 158 | return 159 | } 160 | 161 | if ic.queue.NumRequeues(key) < maxRetries { 162 | ic.queue.AddRateLimited(key) 163 | return 164 | } 165 | 166 | beego.Warn(fmt.Sprintf("Dropping ingress %q out of the queue: %v, cluster: %s", key, err, ic.cluster)) 167 | ic.queue.Forget(key) 168 | } 169 | 170 | // syncIngress will sync the ingress with the given key. 171 | // This function is not meant to be invoked concurrently with the same key. 172 | func (ic *IngressController) syncIngress(key string) error { 173 | namespace, name, err := cache.SplitMetaNamespaceKey(key) 174 | if err != nil { 175 | return err 176 | } 177 | //check namespace 178 | if util.FilterNamespace(ic.cluster, namespace) { 179 | //beego.Warn("Skip this syncing of cluster "+ic.cluster, namespace) 180 | return nil 181 | } 182 | ing, err := ic.ingLister.Ingresses(namespace).Get(name) 183 | if errors.IsNotFound(err) { 184 | err = ic.deleteIngressRecord(namespace, name) 185 | if err != nil { 186 | beego.Error("Delete ingress from database failed: ", err) 187 | } 188 | return err 189 | } 190 | if err != nil { 191 | return err 192 | } 193 | 194 | return ic.syncIngressRecord(*ing) 195 | } 196 | -------------------------------------------------------------------------------- /backend/controllers/ingress/ingressutil.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "kubecloud/backend/models" 5 | commutil "kubecloud/common/utils" 6 | 7 | extensions "k8s.io/api/extensions/v1beta1" 8 | ) 9 | 10 | func genIngressRecord(cluster string, ing extensions.Ingress) models.K8sIngress { 11 | record := models.K8sIngress{} 12 | record.Name = ing.Name 13 | record.Namespace = ing.Namespace 14 | record.Cluster = cluster 15 | record.Annotation = commutil.SimpleJsonMarshal(ing.Annotations, "") 16 | for _, rule := range ing.Spec.Rules { 17 | if rule.Host == "" { 18 | continue 19 | } 20 | ruleList := genIngressRuleRecords(cluster, ing, rule) 21 | record.Rules = append(record.Rules, ruleList...) 22 | } 23 | return record 24 | } 25 | 26 | func genIngressRuleRecords(cluster string, ing extensions.Ingress, rule extensions.IngressRule) []*models.K8sIngressRule { 27 | recordList := []*models.K8sIngressRule{} 28 | record := models.K8sIngressRule{} 29 | record.Namespace = ing.Namespace 30 | record.Cluster = cluster 31 | record.IngressName = ing.Name 32 | record.IsTls = false 33 | record.Host = rule.Host 34 | for _, tls := range ing.Spec.TLS { 35 | for _, h := range tls.Hosts { 36 | if h == rule.Host { 37 | record.IsTls = true 38 | record.SecretName = tls.SecretName 39 | break 40 | } 41 | } 42 | if record.IsTls { 43 | break 44 | } 45 | } 46 | // just one path 47 | if rule.HTTP != nil { 48 | for _, path := range rule.HTTP.Paths { 49 | item := models.K8sIngressRule{} 50 | item = record 51 | item.Path = path.Path 52 | item.ServiceName = path.Backend.ServiceName 53 | item.ServicePort = path.Backend.ServicePort.IntValue() 54 | recordList = append(recordList, &item) 55 | } 56 | } 57 | return recordList 58 | } 59 | -------------------------------------------------------------------------------- /backend/controllers/ingress/syncingress.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "kubecloud/backend/models" 5 | 6 | "github.com/astaxie/beego" 7 | "github.com/astaxie/beego/orm" 8 | extensions "k8s.io/api/extensions/v1beta1" 9 | ) 10 | 11 | // update if the app is existed, or add it 12 | func (ic *IngressController) syncIngressRecord(ing extensions.Ingress) error { 13 | old, err := ic.kubeIngHandler.Get(ic.cluster, ing.Namespace, ing.Name) 14 | if err != nil { 15 | if err != orm.ErrNoRows { 16 | return err 17 | } 18 | err = ic.createIngressRecord(ing) 19 | } else { 20 | err = ic.updateIngressRecord(ing, *old) 21 | } 22 | 23 | return err 24 | } 25 | 26 | func (ic *IngressController) deleteIngressRecord(namespace, name string) error { 27 | // delete ingress 28 | err := ic.kubeIngHandler.Delete(ic.cluster, namespace, name) 29 | if err != nil { 30 | beego.Error("Delete kube ingress record", ic.cluster, namespace, name, "failed for", err) 31 | } 32 | return err 33 | } 34 | 35 | func (ic *IngressController) createIngressRecord(ing extensions.Ingress) error { 36 | record := genIngressRecord(ic.cluster, ing) 37 | if len(record.Rules) == 0 { 38 | beego.Warn("no rules of ingress", ic.cluster, record.Namespace, record.Name) 39 | return nil 40 | } 41 | err := ic.kubeIngHandler.Create(record) 42 | if err != nil { 43 | beego.Error("Create kube ingress record", ic.cluster, record.Namespace, record.Name, "failed for", err) 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | func (ic *IngressController) updateIngressRecord(ing extensions.Ingress, old models.K8sIngress) error { 50 | record := genIngressRecord(ic.cluster, ing) 51 | err := ic.kubeIngHandler.Update(old, record) 52 | if err != nil { 53 | beego.Error("Update kube ingress", ic.cluster, record.Namespace, record.Name, "failed for", err) 54 | return err 55 | } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /backend/controllers/secret/secretutil.go: -------------------------------------------------------------------------------- 1 | package secret 2 | 3 | import ( 4 | "kubecloud/backend/controllers/util" 5 | "kubecloud/backend/models" 6 | "kubecloud/backend/resource" 7 | "time" 8 | 9 | "encoding/json" 10 | 11 | "github.com/astaxie/beego" 12 | core "k8s.io/api/core/v1" 13 | ) 14 | 15 | //generate secret template by Secret. return K8sSecret struct 16 | func genSecretRecord(cluster string, s core.Secret) models.K8sSecret { 17 | record := models.K8sSecret{ 18 | Cluster: cluster, 19 | Namespace: s.Namespace, 20 | Name: s.Name, 21 | Addons: models.NewAddons(), 22 | } 23 | record.CreateAt, _ = time.Parse("2006-01-02 15:04:05", s.CreationTimestamp.Local().Format("2006-01-02 15:04:05")) 24 | record.OwnerName = util.GetAnnotationStringValue(resource.OwnerNameAnnotationKey, 25 | s.Annotations, "") 26 | record.Description = util.GetAnnotationStringValue(resource.DescriptionAnnotationKey, 27 | s.Annotations, "") 28 | record.Type = string(s.Type) 29 | data, err := json.Marshal(&s.Data) 30 | if err != nil { 31 | beego.Error("marshal secret data failed:", s.Name, err) 32 | } else { 33 | record.Data = string(data) 34 | } 35 | 36 | return record 37 | } 38 | 39 | func secretIsEqual(os, ns models.K8sSecret) bool { 40 | equal := true 41 | if os.Type != ns.Type || 42 | os.Description != ns.Description || 43 | os.Data != ns.Data { 44 | equal = false 45 | } 46 | return equal 47 | } 48 | -------------------------------------------------------------------------------- /backend/controllers/secret/syncsecret.go: -------------------------------------------------------------------------------- 1 | package secret 2 | 3 | import ( 4 | "kubecloud/backend/models" 5 | 6 | "github.com/astaxie/beego" 7 | "github.com/astaxie/beego/orm" 8 | core "k8s.io/api/core/v1" 9 | ) 10 | 11 | func (sc *SecretController) syncSecretRecord(s core.Secret) error { 12 | old, err := sc.secretHandler.GetSecret(sc.cluster, s.Namespace, s.Name) 13 | if err != nil { 14 | if err != orm.ErrNoRows { 15 | return err 16 | } 17 | err = sc.createSecretRecord(s) 18 | } else { 19 | err = sc.updateSecretRecord(s, *old) 20 | } 21 | 22 | return err 23 | } 24 | 25 | func (sc *SecretController) deleteSecretRecord(namespace, name string) error { 26 | err := sc.secretHandler.DeleteSecret(sc.cluster, namespace, name) 27 | if err != nil { 28 | beego.Error("Delete kube secret record", sc.cluster, namespace, name, "failed for", err) 29 | } 30 | return err 31 | } 32 | 33 | func (sc *SecretController) createSecretRecord(s core.Secret) error { 34 | record := genSecretRecord(sc.cluster, s) 35 | err := sc.secretHandler.CreateSecret(record, false) 36 | if err != nil { 37 | beego.Error("Create kube secret record", sc.cluster, record.Namespace, record.Name, "failed for", err) 38 | return err 39 | } 40 | return nil 41 | } 42 | 43 | func (sc *SecretController) updateSecretRecord(s core.Secret, old models.K8sSecret) error { 44 | record := genSecretRecord(sc.cluster, s) 45 | if !secretIsEqual(old, record) { 46 | err := sc.secretHandler.UpdateSecret(old, record, false) 47 | if err != nil { 48 | beego.Error("Update kube secret", sc.cluster, record.Namespace, record.Name, "failed for", err) 49 | return err 50 | } 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /backend/controllers/service/serviceutil.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "kubecloud/backend/controllers/util" 5 | "kubecloud/backend/models" 6 | "kubecloud/backend/resource" 7 | commutil "kubecloud/common/utils" 8 | 9 | core "k8s.io/api/core/v1" 10 | ) 11 | 12 | //generate service template by service. return service struct 13 | func genServiceRecord(cluster string, svc core.Service) models.K8sService { 14 | record := models.K8sService{ 15 | Cluster: cluster, 16 | Namespace: svc.Namespace, 17 | Name: svc.Name, 18 | } 19 | record.OwnerName = util.GetAnnotationStringValue(resource.OwnerNameAnnotationKey, 20 | svc.Annotations, resource.GetApplicationNameBySvcName(svc.Name)) 21 | record.Type = string(svc.Spec.Type) 22 | record.ClusterIP = svc.Spec.ClusterIP 23 | record.Annotation = commutil.SimpleJsonMarshal(svc.Annotations, "") 24 | for _, item := range svc.Spec.Ports { 25 | port := models.K8sServicePort{ 26 | Name: item.Name, 27 | Cluster: cluster, 28 | Namespace: svc.Namespace, 29 | ServiceName: svc.Name, 30 | Protocol: string(item.Protocol), 31 | Port: int(item.Port), 32 | TargetPort: item.TargetPort.IntValue(), 33 | NodePort: int(item.NodePort), 34 | } 35 | record.Ports = append(record.Ports, &port) 36 | } 37 | 38 | return record 39 | } 40 | 41 | func serviceIsEqual(os, ns models.K8sService) bool { 42 | equal := true 43 | if os.ClusterIP != ns.ClusterIP || 44 | os.Type != ns.Type || 45 | len(os.Ports) != len(ns.Ports) || 46 | os.Annotation != ns.Annotation { 47 | equal = false 48 | } 49 | for _, np := range ns.Ports { 50 | if !equal { 51 | break 52 | } 53 | exist := false 54 | for _, op := range os.Ports { 55 | if np.Name == op.Name { 56 | exist = true 57 | if np.Port != op.Port || 58 | np.TargetPort != op.TargetPort || 59 | np.Protocol != op.Protocol || 60 | np.NodePort != op.NodePort { 61 | equal = false 62 | } 63 | break 64 | } 65 | } 66 | if !exist { 67 | equal = false 68 | } 69 | } 70 | return equal 71 | } 72 | -------------------------------------------------------------------------------- /backend/controllers/service/syncservice.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "kubecloud/backend/controllers/util" 5 | "kubecloud/backend/models" 6 | "kubecloud/backend/resource" 7 | 8 | "github.com/astaxie/beego" 9 | "github.com/astaxie/beego/orm" 10 | core "k8s.io/api/core/v1" 11 | ) 12 | 13 | // update if the app is existed, or add it 14 | func (sc *ServiceController) syncServiceRecord(svc core.Service) error { 15 | old, err := sc.svcHandler.Get(sc.cluster, svc.Namespace, 16 | util.GetAnnotationStringValue(resource.OwnerNameAnnotationKey, svc.Annotations, resource.GetApplicationNameBySvcName(svc.Name)), 17 | svc.Name) 18 | if err != nil { 19 | if err != orm.ErrNoRows { 20 | return err 21 | } 22 | err = sc.createServiceRecord(svc) 23 | } else { 24 | err = sc.updateServiceRecord(svc, *old) 25 | } 26 | return err 27 | } 28 | 29 | func (sc *ServiceController) deleteServiceRecord(namespace, name string) error { 30 | // delete service 31 | err := sc.svcHandler.Delete(sc.cluster, namespace, name) 32 | if err != nil { 33 | beego.Error("Delete kube service record", sc.cluster, namespace, name, "failed for", err) 34 | } 35 | return err 36 | } 37 | 38 | func (sc *ServiceController) createServiceRecord(svc core.Service) error { 39 | record := genServiceRecord(sc.cluster, svc) 40 | err := sc.svcHandler.Create(record) 41 | if err != nil { 42 | beego.Error("Create kube service record", sc.cluster, record.Namespace, record.Name, "failed for", err) 43 | return err 44 | } 45 | return nil 46 | } 47 | 48 | func (sc *ServiceController) updateServiceRecord(svc core.Service, old models.K8sService) error { 49 | record := genServiceRecord(sc.cluster, svc) 50 | if !serviceIsEqual(old, record) { 51 | record.Id = old.Id 52 | } 53 | if record.Id != 0 { 54 | err := sc.svcHandler.Update(old, record) 55 | if err != nil { 56 | beego.Error("Update kube service", sc.cluster, record.Namespace, record.Name, "failed for", err) 57 | return err 58 | } 59 | } 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /backend/controllers/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "kubecloud/backend/dao" 5 | ) 6 | 7 | var filterNamespaces = []string{"default", "kube-system", "kube-public", "tekton-pipelines"} 8 | 9 | func GetAnnotationStringValue(key string, ann map[string]string, def string) string { 10 | if ann == nil { 11 | return def 12 | } 13 | if value, exist := ann[key]; exist { 14 | return value 15 | } 16 | return def 17 | } 18 | 19 | func GetAnnotationBoolValue(key string, ann map[string]string, def bool) bool { 20 | if ann == nil { 21 | return def 22 | } 23 | if value, exist := ann[key]; exist { 24 | return value == "true" || value == "TRUE" 25 | } 26 | return def 27 | } 28 | 29 | func FilterNamespace(cluster string, namespace string) bool { 30 | for _, ns := range filterNamespaces { 31 | if ns == namespace { 32 | return true 33 | } 34 | } 35 | return !dao.NamespaceExists(cluster, namespace) 36 | } 37 | -------------------------------------------------------------------------------- /backend/dao/appversion.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/astaxie/beego/orm" 7 | "kubecloud/backend/models" 8 | ) 9 | 10 | type VersionModel struct { 11 | tOrmer orm.Ormer 12 | TableName string 13 | } 14 | 15 | func NewVersionModel() *VersionModel { 16 | return &VersionModel{ 17 | tOrmer: GetOrmer(), 18 | TableName: (&models.ZcloudVersion{}).TableName(), 19 | } 20 | } 21 | 22 | func (vm *VersionModel) GetVersionList(cluster, namespace, appname string) ([]models.ZcloudVersion, error) { 23 | list := []models.ZcloudVersion{} 24 | 25 | query := vm.tOrmer.QueryTable(vm.TableName). 26 | Filter("cluster", cluster). 27 | Filter("namespace", namespace). 28 | Filter("name", appname) 29 | _, err := query.All(&list) 30 | if err == orm.ErrNoRows { 31 | return list, nil 32 | } 33 | for i, item := range list { 34 | // compatible with old application 35 | if item.PodVersion == "" { 36 | list[i].PodVersion = item.Version 37 | } 38 | } 39 | return list, err 40 | } 41 | 42 | func (vm *VersionModel) GetVersion(cluster, namespace, appname, version string) (*models.ZcloudVersion, error) { 43 | v := models.ZcloudVersion{} 44 | 45 | query := vm.tOrmer.QueryTable(vm.TableName). 46 | Filter("cluster", cluster). 47 | Filter("namespace", namespace). 48 | Filter("name", appname). 49 | Filter("version", version) 50 | err := query.One(&v) 51 | if err != nil { 52 | return nil, err 53 | } 54 | // compatible with old application 55 | if v.PodVersion == "" { 56 | v.PodVersion = v.Version 57 | } 58 | return &v, nil 59 | } 60 | 61 | func (vm *VersionModel) SetVersionWeight(version *models.ZcloudVersion) error { 62 | if version == nil { 63 | return fmt.Errorf("version must be given!") 64 | } 65 | v, err := vm.GetVersion(version.Cluster, version.Namespace, version.Name, version.Version) 66 | if err == orm.ErrNoRows { 67 | // insert 68 | version.Addons = models.NewAddons() 69 | _, err = vm.tOrmer.Insert(version) 70 | } else { 71 | if err != nil { 72 | return err 73 | } 74 | v.Weight = version.Weight 75 | v.CurReplicas = version.CurReplicas 76 | v.Addons = v.Addons.UpdateAddons() 77 | _, err = vm.tOrmer.Update(v) 78 | } 79 | return err 80 | } 81 | 82 | func (vm *VersionModel) DeleteVersion(cluster, namespace, appname, version string) error { 83 | _, err := vm.tOrmer.Raw("DELETE FROM "+vm.TableName+" WHERE cluster=? AND namespace=? AND name=? AND version=?", 84 | cluster, namespace, appname, version).Exec() 85 | return err 86 | } 87 | 88 | func (vm *VersionModel) DeleteAllVersion(cluster, namespace, appname string) error { 89 | _, err := vm.tOrmer.Raw("DELETE FROM "+vm.TableName+" WHERE cluster=? AND namespace=? AND name=?", 90 | cluster, namespace, appname).Exec() 91 | return err 92 | } 93 | -------------------------------------------------------------------------------- /backend/dao/cluster.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/astaxie/beego/orm" 7 | 8 | "kubecloud/backend/models" 9 | "kubecloud/common/utils" 10 | ) 11 | 12 | var clusterEnableFilterKeys = []string{ 13 | "name", 14 | "display_name", 15 | "env", 16 | "registry_name", 17 | "status", 18 | "domain_suffix", 19 | "creator", 20 | "create_at", 21 | } 22 | 23 | func GetClusterListByFilter(filterQuery *utils.FilterQuery) (*utils.QueryResult, error) { 24 | var clusters []models.ZcloudCluster 25 | queryCond := orm.NewCondition().And("deleted", 0) 26 | if filterQuery != nil { 27 | filterCond := filterQuery.FilterCondition(clusterEnableFilterKeys) 28 | if filterCond != nil { 29 | queryCond = queryCond.AndCond(filterCond) 30 | } 31 | } 32 | 33 | query := GetOrmer().QueryTable("zcloud_cluster").OrderBy("-create_at").SetCond(queryCond) 34 | 35 | count, err := query.Count() 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | if filterQuery != nil && filterQuery.PageSize != 0 && filterQuery.PageIndex > 0 { 41 | query = query.Limit(filterQuery.PageSize, filterQuery.PageSize*(filterQuery.PageIndex-1)) 42 | } 43 | if _, err := query.All(&clusters); err != nil { 44 | return nil, err 45 | } 46 | return &utils.QueryResult{ 47 | Base: utils.PageInfo{ 48 | TotalNum: count, 49 | PageIndex: filterQuery.PageIndex, 50 | PageSize: filterQuery.PageSize, 51 | }, 52 | List: clusters}, err 53 | } 54 | 55 | func GetClusterList() ([]models.ZcloudCluster, error) { 56 | var clusters []models.ZcloudCluster 57 | qs := GetOrmer().QueryTable("zcloud_cluster").Filter("deleted", 0).OrderBy("-create_at") 58 | _, err := qs.All(&clusters) 59 | return clusters, err 60 | } 61 | 62 | func GetAllClusters() ([]models.ZcloudCluster, error) { 63 | return GetClusterList() 64 | } 65 | 66 | func GetClusterByTenant(tenant, name string) (*models.ZcloudCluster, error) { 67 | var cluster models.ZcloudCluster 68 | if err := GetOrmer().QueryTable("zcloud_cluster"). 69 | Filter("tenant", tenant).Filter("name", name).One(&cluster); err != nil { 70 | return nil, err 71 | } 72 | return &cluster, nil 73 | } 74 | 75 | func GetCluster(clusterId string) (*models.ZcloudCluster, error) { 76 | var cluster models.ZcloudCluster 77 | if err := GetOrmer().QueryTable("zcloud_cluster"). 78 | Filter("cluster_id", clusterId).One(&cluster); err != nil { 79 | if err == orm.ErrMultiRows { 80 | return nil, err 81 | } 82 | if err == orm.ErrNoRows { 83 | return nil, err 84 | } 85 | return nil, fmt.Errorf("database error: get cluster(%s) info failed: %s!", clusterId, err.Error()) 86 | } 87 | 88 | return &cluster, nil 89 | } 90 | 91 | func CreateCluster(cluster models.ZcloudCluster) error { 92 | if _, err := GetOrmer().Insert(&cluster); err != nil { 93 | return fmt.Errorf("database error: insert culster info failed: %s", cluster.Name) 94 | } 95 | return nil 96 | } 97 | 98 | func UpdateCluster(cluster models.ZcloudCluster) error { 99 | if !ClusterNameExist(cluster.ClusterId, cluster.Name) { 100 | return fmt.Errorf("database error: cluster(%s) is not existed!", cluster.Name) 101 | } 102 | 103 | if !HarborIsExist(cluster.Tenant, cluster.Registry) { 104 | return fmt.Errorf("default registry(%s) is not existed!", cluster.Registry) 105 | } 106 | 107 | _, err := GetOrmer().Update(&cluster) 108 | 109 | return err 110 | } 111 | 112 | func DeleteCluster(clusterId string) error { 113 | _, err := GetOrmer().Raw("delete from zcloud_cluster WHERE cluster_id=?", clusterId).Exec() 114 | return err 115 | } 116 | 117 | func ClusterIsExist(clusterId string) bool { 118 | return GetOrmer().QueryTable("zcloud_cluster").Filter("cluster_id", clusterId).Exist() 119 | } 120 | 121 | func ClusterNameExist(id, name string) bool { 122 | return GetOrmer().QueryTable("zcloud_cluster").Filter("cluster_id", id).Filter("name", name).Exist() 123 | } 124 | 125 | func IsClusterParamExistInTenant(tenant, fieldName, fieldValue string) bool { 126 | return GetOrmer().QueryTable("zcloud_cluster").Filter("tenant", tenant).Filter(fieldName, fieldValue).Exist() 127 | } 128 | 129 | func UpdateClusterStatus(clusterId, status string) error { 130 | _, err := GetOrmer().Raw("update zcloud_cluster set status = ? where cluster_id = ?", status, clusterId).Exec() 131 | return err 132 | } 133 | 134 | func GetClusterDomainSuffixList(clusterId string) ([]*models.ZcloudClusterDomainSuffix, error) { 135 | domainSuffixList := []*models.ZcloudClusterDomainSuffix{} 136 | _, err := GetOrmer().QueryTable("zcloud_cluster_domain_suffix").Filter("cluster", clusterId).OrderBy("-create_at").All(&domainSuffixList) 137 | return domainSuffixList, err 138 | } 139 | 140 | func AddClusterDomainSuffix(domainSuffix *models.ZcloudClusterDomainSuffix) error { 141 | _, err := GetOrmer().Insert(domainSuffix) 142 | return err 143 | } 144 | 145 | func DeleteClusterDomainSuffix(clusterId string) error { 146 | _, err := GetOrmer().Raw("delete from zcloud_cluster_domain_suffix where cluster=?", clusterId).Exec() 147 | return err 148 | } 149 | -------------------------------------------------------------------------------- /backend/dao/common.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/astaxie/beego/orm" 7 | ) 8 | 9 | var globalOrm orm.Ormer 10 | var once sync.Once 11 | 12 | // GetOrmer get ormer singleton. Pitfall: each transaction requires a separate orm 13 | func GetOrmer() orm.Ormer { 14 | once.Do(func() { 15 | globalOrm = orm.NewOrm() 16 | }) 17 | return globalOrm 18 | } 19 | -------------------------------------------------------------------------------- /backend/dao/event.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | "kubecloud/backend/models" 6 | "kubecloud/common/utils" 7 | 8 | "github.com/astaxie/beego/orm" 9 | ) 10 | 11 | var eventEnableFilterKeys = []string{ 12 | "event_type", 13 | "source_component", 14 | "source_host", 15 | "object_kind", 16 | "object_name", 17 | "reason", 18 | "message", 19 | "last_time", 20 | } 21 | 22 | func CreateEvent(event models.ZcloudEvent) error { 23 | _, err := GetOrmer().InsertOrUpdate(&event) 24 | return err 25 | } 26 | 27 | func GetEvents(clusterName, namespace, sourceHost, objectKind, objectName, eventLevel string, limitCount int64) ([]models.ZcloudEvent, error) { 28 | var events []models.ZcloudEvent 29 | qs := GetOrmer().QueryTable("zcloud_event").OrderBy("-last_time") 30 | if clusterName != "" { 31 | qs = qs.Filter("cluster", clusterName) 32 | } 33 | if namespace != "" { 34 | qs = qs.Filter("namespace", namespace) 35 | } 36 | if sourceHost != "" { 37 | qs = qs.Filter("source_host", sourceHost) 38 | } 39 | if eventLevel != "" { 40 | qs = qs.Filter("event_level", eventLevel) 41 | } 42 | 43 | if objectKind != "" { 44 | switch objectKind { 45 | case "Pod", "Node": 46 | qs = qs.Filter("object_kind", objectKind) 47 | default: 48 | err := fmt.Errorf("don't supported object kind: %s", objectKind) 49 | return events, err 50 | } 51 | } 52 | if objectName != "" { 53 | qs = qs.Filter("object_name", objectName) 54 | } 55 | 56 | if limitCount != -1 { 57 | qs = qs.Limit(limitCount) 58 | } 59 | if _, err := qs.All(&events); err != nil { 60 | return events, err 61 | } 62 | 63 | return events, nil 64 | } 65 | 66 | func GetAppEvents(cluster, namespace string, app string) ([]*models.ZcloudEvent, error) { 67 | events := []*models.ZcloudEvent{} 68 | sql := `select * from zcloud_event where cluster=? and namespace=? and object_name like '` + app + "%' order by last_time desc" 69 | if _, err := GetOrmer().Raw(sql, cluster, namespace).QueryRows(&events); err != nil { 70 | return nil, err 71 | } 72 | return events, nil 73 | } 74 | 75 | func GetNodeEvents(cluster, host string) ([]*models.ZcloudEvent, error) { 76 | events := []*models.ZcloudEvent{} 77 | ormer := GetOrmer() 78 | _, err := ormer.QueryTable("zcloud_event"). 79 | Filter("cluster", cluster). 80 | Filter("object_kind", "Node"). 81 | Filter("source_host", host). 82 | OrderBy("-last_time").All(&events) 83 | return events, err 84 | } 85 | 86 | func GetNodeEventsByFilter(cluster, host string, filterQuery *utils.FilterQuery) (*utils.QueryResult, error) { 87 | var events []models.ZcloudEvent 88 | queryCond := orm.NewCondition().And("cluster", cluster).And("source_host", host) 89 | if filterQuery != nil { 90 | filterCond := filterQuery.FilterCondition(eventEnableFilterKeys) 91 | if filterCond != nil { 92 | queryCond = queryCond.AndCond(filterCond) 93 | } 94 | } 95 | 96 | query := GetOrmer().QueryTable("zcloud_event").OrderBy("-last_time").SetCond(queryCond) 97 | 98 | count, err := query.Count() 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | if filterQuery != nil && filterQuery.PageSize != 0 && filterQuery.PageIndex > 0 { 104 | query = query.Limit(filterQuery.PageSize, filterQuery.PageSize*(filterQuery.PageIndex-1)) 105 | } 106 | if _, err := query.All(&events); err != nil { 107 | return nil, err 108 | } 109 | return &utils.QueryResult{ 110 | Base: utils.PageInfo{ 111 | TotalNum: count, 112 | PageIndex: filterQuery.PageIndex, 113 | PageSize: filterQuery.PageSize, 114 | }, 115 | List: events}, err 116 | } 117 | -------------------------------------------------------------------------------- /backend/dao/k8s_endpoint.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | models "kubecloud/backend/models" 5 | 6 | "github.com/astaxie/beego" 7 | "github.com/astaxie/beego/orm" 8 | ) 9 | 10 | type K8sEndpointModel struct { 11 | tOrmer orm.Ormer 12 | endpointTable string 13 | addressTable string 14 | } 15 | 16 | func NewK8sEndpointModel() *K8sEndpointModel { 17 | return &K8sEndpointModel{ 18 | tOrmer: GetOrmer(), 19 | endpointTable: (&models.K8sEndpoint{}).TableName(), 20 | addressTable: (&models.K8sEndpointAddress{}).TableName(), 21 | } 22 | } 23 | 24 | func (em *K8sEndpointModel) Create(endpoint models.K8sEndpoint) error { 25 | endpoint.Addons = models.NewAddons() 26 | _, err := em.tOrmer.Insert(&endpoint) 27 | if err != nil { 28 | return err 29 | } 30 | var addresses []models.K8sEndpointAddress 31 | for _, item := range endpoint.Addresses { 32 | addr := *item 33 | addr.Endpoint = &endpoint 34 | addr.Addons = models.NewAddons() 35 | addresses = append(addresses, addr) 36 | } 37 | _, err = em.tOrmer.InsertMulti(len(addresses), addresses) 38 | return err 39 | } 40 | 41 | func (em *K8sEndpointModel) Update(old, cur models.K8sEndpoint) error { 42 | cur.Id = old.Id 43 | if !em.endpointBaseIsEqual(old, cur) { 44 | cur.OwnerName = old.OwnerName 45 | if err := em.updateEndpoint(old, cur); err != nil { 46 | return err 47 | } 48 | } 49 | for _, na := range cur.Addresses { 50 | index := -1 51 | for i, oa := range old.Addresses { 52 | if em.addressIsEqual(*na, *oa) { 53 | index = i 54 | } 55 | } 56 | if index == -1 { 57 | // create 58 | if err := em.insertAddress(na, &cur); err != nil { 59 | return err 60 | } 61 | } 62 | } 63 | // delete old other 64 | for _, oa := range old.Addresses { 65 | index := -1 66 | for i, na := range cur.Addresses { 67 | if em.addressIsEqual(*oa, *na) { 68 | index = i 69 | break 70 | } 71 | } 72 | if index == -1 { 73 | err := em.deleteAddress(oa.Id) 74 | if err != nil { 75 | beego.Error(err) 76 | } 77 | } 78 | } 79 | 80 | return nil 81 | } 82 | 83 | func (em *K8sEndpointModel) Delete(cluster, namespace, name string) error { 84 | err := em.deleteEndpoint(cluster, namespace, name) 85 | if err == nil { 86 | err = em.deleteAddresses(cluster, namespace, name) 87 | } 88 | return err 89 | } 90 | 91 | func (em *K8sEndpointModel) DeleteByID(id int64) error { 92 | sql := "delete from " + em.endpointTable + " where id=?" 93 | _, err := em.tOrmer.Raw(sql, id).Exec() 94 | if err == nil { 95 | sql := "delete from " + em.addressTable + " where endpoint=?" 96 | _, err = em.tOrmer.Raw(sql, id).Exec() 97 | } 98 | return err 99 | } 100 | 101 | func (em *K8sEndpointModel) Get(cluster, namespace, name string, port int32) (*models.K8sEndpoint, error) { 102 | var obj models.K8sEndpoint 103 | 104 | if err := em.tOrmer.QueryTable(em.endpointTable). 105 | Filter("cluster", cluster). 106 | Filter("namespace", namespace). 107 | Filter("name", name). 108 | Filter("port", port). 109 | Filter("deleted", 0).One(&obj); err != nil { 110 | beego.Error(err) 111 | return nil, err 112 | } 113 | if _, err := em.tOrmer.LoadRelated(&obj, "addresses"); err != nil { 114 | beego.Error(err) 115 | return nil, err 116 | } 117 | var addresses []*models.K8sEndpointAddress 118 | for _, addr := range obj.Addresses { 119 | if addr.Deleted == 0 { 120 | addresses = append(addresses, addr) 121 | } 122 | } 123 | obj.Addresses = addresses 124 | return &obj, nil 125 | } 126 | 127 | func (em *K8sEndpointModel) ListByName(cluster, namespace, name string) ([]models.K8sEndpoint, error) { 128 | list := []models.K8sEndpoint{} 129 | var err error 130 | 131 | query := em.tOrmer.QueryTable(em.endpointTable). 132 | Filter("cluster", cluster). 133 | Filter("namespace", namespace). 134 | Filter("name", name). 135 | Filter("deleted", 0).OrderBy("-create_at") 136 | _, err = query.All(&list) 137 | if err != nil { 138 | return list, err 139 | } 140 | 141 | return list, nil 142 | } 143 | 144 | func (im *K8sEndpointModel) updateEndpoint(old, cur models.K8sEndpoint) error { 145 | cur.Addons = old.Addons.UpdateAddons() 146 | _, err := im.tOrmer.Update(&cur) 147 | return err 148 | } 149 | 150 | func (em *K8sEndpointModel) deleteEndpoint(cluster, namespace, name string) error { 151 | sql := "delete from " + em.endpointTable + " where cluster=? and namespace=? and name=?" 152 | _, err := em.tOrmer.Raw(sql, cluster, namespace, name).Exec() 153 | return err 154 | } 155 | 156 | func (im *K8sEndpointModel) insertAddress(addr *models.K8sEndpointAddress, endpoint *models.K8sEndpoint) error { 157 | addr.Addons = models.NewAddons() 158 | addr.Endpoint = endpoint 159 | _, err := im.tOrmer.Insert(addr) 160 | return err 161 | } 162 | 163 | func (em *K8sEndpointModel) deleteAddresses(cluster, namespace, name string) error { 164 | sql := "delete from " + em.addressTable + " where cluster=? and namespace=? and endpoint_name=?" 165 | _, err := em.tOrmer.Raw(sql, cluster, namespace, name).Exec() 166 | 167 | return err 168 | } 169 | 170 | func (em *K8sEndpointModel) deleteAddress(id int64) error { 171 | sql := "delete from " + em.addressTable + " where id=?" 172 | _, err := em.tOrmer.Raw(sql, id).Exec() 173 | 174 | return err 175 | } 176 | 177 | func (em *K8sEndpointModel) addressIsEqual(a1, a2 models.K8sEndpointAddress) bool { 178 | if a1.Cluster != a2.Cluster || 179 | a1.Namespace != a2.Namespace || 180 | a1.EndpointName != a2.EndpointName || 181 | a1.IP != a2.IP || 182 | a1.NodeName != a2.NodeName || 183 | a1.TargetRefName != a2.TargetRefName { 184 | return false 185 | } 186 | return true 187 | } 188 | 189 | func (em *K8sEndpointModel) endpointBaseIsEqual(e1, e2 models.K8sEndpoint) bool { 190 | if e1.OwnerName != e2.OwnerName || 191 | e1.Port != e2.Port || 192 | e1.PortName != e2.PortName || 193 | e1.Protocol != e2.Protocol { 194 | return false 195 | } 196 | 197 | return true 198 | } 199 | -------------------------------------------------------------------------------- /backend/dao/k8s_ingress_rule.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/astaxie/beego" 7 | "github.com/astaxie/beego/orm" 8 | models "kubecloud/backend/models" 9 | "kubecloud/common/utils" 10 | ) 11 | 12 | type IngressRuleModel struct { 13 | tOrmer orm.Ormer 14 | TableName string 15 | } 16 | 17 | type hostOwner struct { 18 | cluster string 19 | namespace string 20 | } 21 | 22 | const ( 23 | SIMPLE_UNIQUE_MODE = "simple" 24 | ) 25 | 26 | var ruleEnableFilterKeys = map[string]interface{}{ 27 | "namespace": nil, 28 | "ingress_name": nil, 29 | "secret_name": nil, 30 | "service_name": nil, 31 | "host": nil, 32 | "creator": nil, 33 | "create_at": nil, 34 | } 35 | 36 | func NewIngressRuleModel() *IngressRuleModel { 37 | return &IngressRuleModel{ 38 | tOrmer: GetOrmer(), 39 | TableName: (&models.K8sIngressRule{}).TableName(), 40 | } 41 | } 42 | 43 | func (im *IngressRuleModel) List(cluster string, nslist []string, svcname string, filterQuery *utils.FilterQuery) (*utils.QueryResult, error) { 44 | rulelist := []models.K8sIngressRule{} 45 | query := im.tOrmer.QueryTable(im.TableName). 46 | Filter("deleted", 0).OrderBy("-create_at") 47 | if cluster != "" { 48 | query = query.Filter("cluster", cluster) 49 | } 50 | if len(nslist) >= 1 { 51 | query = query.Filter("namespace__in", nslist) 52 | } 53 | if svcname != "" { 54 | query = query.Filter("service_name", svcname) 55 | } 56 | if filterQuery.FilterVal != "" { 57 | if _, exist := ruleEnableFilterKeys[filterQuery.FilterKey]; exist { 58 | suffix := "__icontains" 59 | if !filterQuery.IsLike { 60 | suffix = "" 61 | } 62 | query = query.Filter(filterQuery.FilterKey+suffix, filterQuery.FilterVal) 63 | } 64 | } 65 | if filterQuery.PageSize != 0 { 66 | realIndex := 0 67 | if filterQuery.PageIndex > 0 { 68 | realIndex = filterQuery.PageIndex - 1 69 | } 70 | query = query.Limit(filterQuery.PageSize, filterQuery.PageSize*realIndex) 71 | } 72 | if _, err := query.RelatedSel().All(&rulelist); err != nil { 73 | return nil, err 74 | } 75 | count, err := query.Count() 76 | if err != nil { 77 | return nil, err 78 | } 79 | return &utils.QueryResult{ 80 | Base: utils.PageInfo{ 81 | TotalNum: count, 82 | PageIndex: filterQuery.PageIndex, 83 | PageSize: filterQuery.PageSize, 84 | }, 85 | List: rulelist}, err 86 | } 87 | 88 | func (im *IngressRuleModel) GetByID(cluster, namespace string, id int64) (*models.K8sIngressRule, error) { 89 | var rule models.K8sIngressRule 90 | 91 | if err := im.tOrmer.QueryTable(im.TableName). 92 | Filter("id", id). 93 | Filter("cluster", cluster). 94 | Filter("namespace", namespace). 95 | Filter("deleted", 0).RelatedSel().One(&rule); err != nil { 96 | return nil, err 97 | } 98 | return &rule, nil 99 | } 100 | 101 | func (im *IngressRuleModel) CheckHostUnique(cluster, namespace, host string) error { 102 | //check the host is existed in other cluster or other namespace 103 | filter := &utils.FilterQuery{} 104 | filter.FilterKey = "host" 105 | filter.FilterVal = host 106 | filterCluster := "" 107 | if beego.AppConfig.String("other::hostUniqueMode") == SIMPLE_UNIQUE_MODE { 108 | filterCluster = cluster 109 | } 110 | res, err := im.List(filterCluster, nil, "", filter) 111 | if err != nil { 112 | return err 113 | } 114 | ownerList := []hostOwner{} 115 | for _, item := range res.List.([]models.K8sIngressRule) { 116 | if item.Cluster != cluster || item.Namespace != namespace { 117 | ownerList = append(ownerList, hostOwner{cluster: item.Cluster, namespace: item.Namespace}) 118 | } 119 | } 120 | if len(ownerList) > 0 { 121 | beego.Warn(fmt.Sprintf("domain name(%s) is existed in cluster(%v)!", host, ownerList)) 122 | return fmt.Errorf("domain name(%s) is existed in cluster!", host) 123 | } 124 | return nil 125 | } 126 | 127 | //just check path is unique under host 128 | func (im *IngressRuleModel) CheckPathsUniqueInHost(cluster, namespace, host string, paths []string, id int64) error { 129 | //check the host is existed in other cluster or other namespace 130 | filter := &utils.FilterQuery{} 131 | filter.FilterKey = "host" 132 | filter.FilterVal = host 133 | res, err := im.List(cluster, []string{namespace}, "", filter) 134 | if err != nil { 135 | return err 136 | } 137 | for _, path := range paths { 138 | for _, item := range res.List.([]models.K8sIngressRule) { 139 | if utils.PathsIsEqual(item.Path, path) { 140 | if (id > 0 && item.Id != id) || id < 0 { 141 | return fmt.Errorf("path(%s) is existed in domain(%s)!", utils.GetRootPath(path), host) 142 | } 143 | } 144 | } 145 | } 146 | return nil 147 | } 148 | 149 | //just check path is unique under host 150 | func (im *IngressRuleModel) GetIngressNameByHost(cluster, namespace, host string) string { 151 | var rule models.K8sIngressRule 152 | 153 | if err := im.tOrmer.QueryTable(im.TableName). 154 | Filter("cluster", cluster). 155 | Filter("namespace", namespace). 156 | Filter("host", host). 157 | Filter("deleted", 0).RelatedSel().One(&rule); err != nil { 158 | return "" 159 | } 160 | return rule.IngressName 161 | } 162 | -------------------------------------------------------------------------------- /backend/dao/k8s_namespace.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | "kubecloud/common/utils" 6 | 7 | "kubecloud/backend/models" 8 | 9 | "github.com/astaxie/beego/orm" 10 | ) 11 | 12 | var nsEnableFilterKeys = []string{ 13 | "cluster", 14 | "name", 15 | "desc", 16 | "cpu_quota", 17 | "memory_quota", 18 | } 19 | 20 | type NamespaceModel struct { 21 | tOrmer orm.Ormer 22 | TableName string 23 | } 24 | 25 | func NewNamespaceModel() *NamespaceModel { 26 | return &NamespaceModel{ 27 | tOrmer: GetOrmer(), 28 | TableName: (&models.K8sNamespace{}).TableName(), 29 | } 30 | } 31 | 32 | func NamespaceList(clusters []string, names []string, filterQuery *utils.FilterQuery) (*utils.QueryResult, error) { 33 | var rows []models.K8sNamespace 34 | PageIndex := 0 35 | PageSize := 0 36 | realIndex := 0 37 | queryCond := orm.NewCondition().And("deleted", 0) 38 | defFilter := utils.NewDefaultFilter().AppendFilter("cluster", clusters, utils.FilterOperatorIn). 39 | AppendFilter("name", names, utils.FilterOperatorIn) 40 | normalCond := defFilter.DefaultFilterCondition() 41 | queryCond = queryCond.AndCond(normalCond) 42 | if filterQuery != nil { 43 | filterCond := filterQuery.FilterCondition(nsEnableFilterKeys) 44 | if filterCond != nil { 45 | queryCond = queryCond.AndCond(filterCond) 46 | } 47 | if filterQuery.PageSize != 0 { 48 | if filterQuery.PageIndex > 0 { 49 | realIndex = filterQuery.PageIndex - 1 50 | } 51 | } 52 | PageIndex = filterQuery.PageIndex 53 | PageSize = filterQuery.PageSize 54 | } 55 | query := GetOrmer().QueryTable("k8s_namespace").OrderBy("-updated_at").SetCond(queryCond) 56 | if PageSize != 0 { 57 | query = query.Limit(PageSize, PageSize*realIndex) 58 | } 59 | if _, err := query.All(&rows); err != nil { 60 | return nil, err 61 | } 62 | count, err := query.Count() 63 | if err != nil { 64 | return nil, err 65 | } 66 | return &utils.QueryResult{ 67 | Base: utils.PageInfo{ 68 | TotalNum: count, 69 | PageIndex: PageIndex, 70 | PageSize: PageSize, 71 | }, 72 | List: rows}, err 73 | } 74 | 75 | func GetClusterNamespaceList(cluster string) ([]string, error) { 76 | var nsList []string 77 | _, err := GetOrmer().QueryTable("k8s_namespace").Filter("cluster", cluster).All(&nsList, "name") 78 | return nsList, err 79 | } 80 | 81 | func NamespaceGet(cluster string, name string) (*models.K8sNamespace, error) { 82 | var row models.K8sNamespace 83 | err := GetOrmer(). 84 | QueryTable("k8s_namespace"). 85 | Filter("cluster", cluster). 86 | Filter("name", name). 87 | Filter("deleted", 0). 88 | One(&row) 89 | return &row, err 90 | } 91 | 92 | func NamespaceInsert(row *models.K8sNamespace) error { 93 | _, err := GetOrmer().Insert(row) 94 | return err 95 | } 96 | 97 | func NamespaceUpdate(row *models.K8sNamespace) error { 98 | _, err := GetOrmer().Update(row) 99 | return err 100 | } 101 | 102 | func NamespaceDelete(cluster, namespace string) error { 103 | ormer := GetOrmer() 104 | if ormer.QueryTable("zcloud_application"). 105 | Filter("cluster", cluster). 106 | Filter("namespace", namespace). 107 | Filter("deleted", 0).Exist() { 108 | return fmt.Errorf("can't delete a namesapce which still has running applications") 109 | } 110 | if ormer.QueryTable("zcloud_job"). 111 | Filter("cluster", cluster). 112 | Filter("namespace", namespace). 113 | Filter("deleted", 0).Exist() { 114 | return fmt.Errorf("can't delete a namesapce which still has running jobs") 115 | } 116 | row, err := NamespaceGet(cluster, namespace) 117 | if err != nil { 118 | return err 119 | } 120 | row.MarkDeleted() 121 | return NamespaceUpdate(row) 122 | } 123 | 124 | func NamespaceExists(cluster, namespace string) bool { 125 | return GetOrmer(). 126 | QueryTable("k8s_namespace"). 127 | Filter("cluster", cluster). 128 | Filter("name", namespace). 129 | Filter("deleted", 0). 130 | Exist() 131 | } 132 | -------------------------------------------------------------------------------- /backend/dao/k8s_secret.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/astaxie/beego/orm" 7 | 8 | "kubecloud/backend/models" 9 | "kubecloud/common/utils" 10 | ) 11 | 12 | type SecretModel struct { 13 | ormer orm.Ormer 14 | TableName string 15 | } 16 | 17 | var secretEnableFilterKeys = map[string]interface{}{ 18 | "namespace": nil, 19 | "name": nil, 20 | "description": nil, 21 | "owner_kind": nil, 22 | "owner_name": nil, 23 | "type": nil, 24 | "creator": nil, 25 | "create_at": nil, 26 | } 27 | 28 | func NewSecretModel() *SecretModel { 29 | return &SecretModel{ 30 | ormer: GetOrmer(), 31 | TableName: (&models.K8sSecret{}).TableName(), 32 | } 33 | } 34 | 35 | func (sm *SecretModel) Exists(cluster, namespace, name string) bool { 36 | return sm.ormer.QueryTable(sm.TableName). 37 | Filter("cluster", cluster). 38 | Filter("namespace", namespace). 39 | Filter("name", name). 40 | Filter("deleted", 0). 41 | Exist() 42 | } 43 | 44 | func (sm *SecretModel) CreateSecret(secret models.K8sSecret, setAddon bool) (err error) { 45 | if setAddon { 46 | secret.Addons = models.NewAddons() 47 | } 48 | _, err = sm.ormer.Insert(&secret) 49 | return 50 | } 51 | 52 | func (sm *SecretModel) UpdateSecret(old, cur models.K8sSecret, setAddon bool) (err error) { 53 | cur.Id = old.Id 54 | if setAddon { 55 | cur.Addons = old.Addons.UpdateAddons() 56 | } 57 | _, err = sm.ormer.Update(&cur) 58 | return 59 | } 60 | 61 | func (sm *SecretModel) DeleteSecret(cluster, namespace, name string) error { 62 | sql := "delete from " + sm.TableName + " where cluster=? and namespace=? and name=?" 63 | _, err := sm.ormer.Raw(sql, cluster, namespace, name).Exec() 64 | 65 | return err 66 | } 67 | 68 | func (sm *SecretModel) GetSecret(cluster, namespace, name string) (*models.K8sSecret, error) { 69 | var secret models.K8sSecret 70 | 71 | if err := sm.ormer.QueryTable(sm.TableName). 72 | Filter("cluster", cluster). 73 | Filter("namespace", namespace). 74 | Filter("name", name). 75 | Filter("deleted", 0).One(&secret); err != nil { 76 | return nil, err 77 | } 78 | 79 | return &secret, nil 80 | } 81 | 82 | func (sm *SecretModel) GetSecretList(cluster string, nslist []string, filterQuery *utils.FilterQuery) (*utils.QueryResult, error) { 83 | secretList := []models.K8sSecret{} 84 | var err error 85 | query := sm.ormer.QueryTable(sm.TableName). 86 | Filter("cluster", cluster). 87 | Filter("deleted", 0).OrderBy("-create_at") 88 | if len(nslist) >= 1 { 89 | query = query.Filter("namespace__in", nslist) 90 | } else { 91 | return nil, fmt.Errorf("namespace must be given!") 92 | } 93 | if filterQuery.FilterVal != "" { 94 | if _, exist := secretEnableFilterKeys[filterQuery.FilterKey]; exist { 95 | query = query.Filter(filterQuery.FilterKey+"__icontains", filterQuery.FilterVal) 96 | } 97 | } 98 | if filterQuery.PageSize != 0 { 99 | realIndex := 0 100 | if filterQuery.PageIndex > 0 { 101 | realIndex = filterQuery.PageIndex - 1 102 | } 103 | query = query.Limit(filterQuery.PageSize, filterQuery.PageSize*realIndex) 104 | } 105 | if _, err := query.All(&secretList); err != nil { 106 | return nil, err 107 | } 108 | count, err := query.Count() 109 | if err != nil { 110 | return nil, err 111 | } 112 | return &utils.QueryResult{ 113 | Base: utils.PageInfo{ 114 | TotalNum: count, 115 | PageIndex: filterQuery.PageIndex, 116 | PageSize: filterQuery.PageSize, 117 | }, 118 | List: secretList}, err 119 | } 120 | -------------------------------------------------------------------------------- /backend/dao/k8s_service.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | models "kubecloud/backend/models" 6 | "kubecloud/common" 7 | 8 | "github.com/astaxie/beego" 9 | "github.com/astaxie/beego/orm" 10 | ) 11 | 12 | type K8sServiceModel struct { 13 | tOrmer orm.Ormer 14 | svcTable string 15 | portTable string 16 | } 17 | 18 | func NewK8sServiceModel() *K8sServiceModel { 19 | return &K8sServiceModel{ 20 | tOrmer: GetOrmer(), 21 | svcTable: (&models.K8sService{}).TableName(), 22 | portTable: (&models.K8sServicePort{}).TableName(), 23 | } 24 | } 25 | 26 | func (km *K8sServiceModel) Create(svc models.K8sService) error { 27 | svc.Addons = models.NewAddons() 28 | _, err := km.tOrmer.Insert(&svc) 29 | if err != nil { 30 | return err 31 | } 32 | var ports []models.K8sServicePort 33 | for _, item := range svc.Ports { 34 | port := *item 35 | port.Service = &svc 36 | port.Addons = models.NewAddons() 37 | ports = append(ports, port) 38 | } 39 | _, err = km.tOrmer.InsertMulti(len(ports), ports) 40 | return err 41 | } 42 | 43 | func (km *K8sServiceModel) Update(oldsvc, newsvc models.K8sService) error { 44 | if err := km.updateService(oldsvc, newsvc); err != nil { 45 | return err 46 | } 47 | for _, np := range newsvc.Ports { 48 | index := -1 49 | for i, op := range oldsvc.Ports { 50 | if np.Name == op.Name { 51 | index = i 52 | break 53 | } 54 | } 55 | if index >= 0 { 56 | // update 57 | if np.Port != oldsvc.Ports[index].Port || 58 | np.TargetPort != oldsvc.Ports[index].TargetPort || 59 | np.Protocol != oldsvc.Ports[index].Protocol || 60 | np.NodePort != oldsvc.Ports[index].NodePort { 61 | if err := km.updatePort(oldsvc.Ports[index], np); err != nil { 62 | return err 63 | } 64 | } 65 | } else { 66 | // create 67 | if err := km.insertPort(np, &newsvc); err != nil { 68 | return err 69 | } 70 | } 71 | } 72 | // delete old other 73 | for _, op := range oldsvc.Ports { 74 | index := -1 75 | for i, np := range newsvc.Ports { 76 | if op.Name == np.Name { 77 | index = i 78 | break 79 | } 80 | } 81 | if index == -1 { 82 | err := km.deletePort(op.Id) 83 | if err != nil { 84 | beego.Error(err) 85 | } 86 | } 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func (km *K8sServiceModel) Delete(cluster, namespace, name string) error { 93 | beego.Debug("delete service: %v", name) 94 | // delete svc 95 | if err := km.deleteService(cluster, namespace, name); err != nil { 96 | return err 97 | } 98 | // delete ports 99 | if err := km.deletePorts(cluster, namespace, name); err != nil { 100 | return err 101 | } 102 | return nil 103 | } 104 | 105 | func (km *K8sServiceModel) List(cluster, namespace string, nodePort int) ([]models.K8sServicePort, error) { 106 | var svcs []models.K8sServicePort 107 | 108 | query := km.tOrmer.QueryTable(km.portTable). 109 | Filter("cluster", cluster). 110 | Filter("deleted", 0) 111 | if namespace != common.AllNamespace { 112 | query = query.Filter("namespace", namespace) 113 | } 114 | if nodePort != 0 { 115 | query = query.Filter("node_port", nodePort) 116 | } 117 | if _, err := query.RelatedSel().All(&svcs); err != nil { 118 | return nil, err 119 | } 120 | 121 | return svcs, nil 122 | } 123 | 124 | func (km *K8sServiceModel) Get(cluster, namespace, oname, name string) (*models.K8sService, error) { 125 | var svc models.K8sService 126 | if oname == "" && name == "" { 127 | return nil, fmt.Errorf("service name or owner name must be given!") 128 | } 129 | query := km.tOrmer.QueryTable(km.svcTable). 130 | Filter("cluster", cluster). 131 | Filter("deleted", 0) 132 | if namespace != "" && namespace != common.AllNamespace { 133 | query = query.Filter("namespace", namespace) 134 | } 135 | if oname != "" { 136 | query = query.Filter("owner_name", oname) 137 | } 138 | if name != "" { 139 | query = query.Filter("name", name) 140 | } 141 | if err := query.One(&svc); err != nil { 142 | return nil, err 143 | } 144 | if _, err := km.tOrmer.LoadRelated(&svc, "ports"); err != nil { 145 | return nil, err 146 | } 147 | var ports []*models.K8sServicePort 148 | for _, port := range svc.Ports { 149 | if port.Deleted == 0 { 150 | ports = append(ports, port) 151 | } 152 | } 153 | svc.Ports = ports 154 | 155 | return &svc, nil 156 | } 157 | 158 | func (km *K8sServiceModel) updateService(oldsvc, newsvc models.K8sService) error { 159 | newsvc.Addons = oldsvc.Addons.UpdateAddons() 160 | _, err := km.tOrmer.Update(&newsvc) 161 | return err 162 | } 163 | 164 | func (km *K8sServiceModel) deleteService(cluster, namespace, name string) error { 165 | sql := "delete from " + km.svcTable + " where cluster=? and namespace=? and name=?" 166 | _, err := km.tOrmer.Raw(sql, cluster, namespace, name).Exec() 167 | return err 168 | } 169 | 170 | func (km *K8sServiceModel) insertPort(port *models.K8sServicePort, svc *models.K8sService) error { 171 | port.Service = svc 172 | port.Addons = models.NewAddons() 173 | _, err := km.tOrmer.Insert(port) 174 | return err 175 | } 176 | 177 | func (km *K8sServiceModel) updatePort(oldport, newport *models.K8sServicePort) error { 178 | newport.Id = oldport.Id 179 | newport.Addons = oldport.Addons.UpdateAddons() 180 | newport.Service = oldport.Service 181 | _, err := km.tOrmer.Update(newport) 182 | return err 183 | } 184 | 185 | func (km *K8sServiceModel) deletePorts(cluster, namespace, name string) error { 186 | sql := "delete from " + km.portTable + " where cluster=? and namespace=? and service_name=?" 187 | _, err := km.tOrmer.Raw(sql, cluster, namespace, name).Exec() 188 | return err 189 | } 190 | 191 | func (km *K8sServiceModel) deletePort(id int64) error { 192 | sql := "delete from " + km.portTable + " where id=?" 193 | _, err := km.tOrmer.Raw(sql, id).Exec() 194 | return err 195 | } 196 | -------------------------------------------------------------------------------- /backend/dao/node.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/astaxie/beego/orm" 7 | 8 | "kubecloud/backend/models" 9 | "kubecloud/common/utils" 10 | ) 11 | 12 | var nodeEnableFilterKeys = []string{ 13 | "name", 14 | "department", 15 | "bizcluster", 16 | "status", 17 | } 18 | 19 | func CheckNodeExist(cluster, ip string) bool { 20 | ormer := GetOrmer() 21 | return ormer.QueryTable("zcloud_node"). 22 | Filter("cluster", cluster). 23 | Filter("name", ip).Exist() 24 | } 25 | 26 | func CreateNode(node models.ZcloudNode) error { 27 | ormer := GetOrmer() 28 | if ormer.QueryTable("zcloud_node"). 29 | Filter("cluster", node.Cluster). 30 | Filter("name", node.Name).Exist() { 31 | return fmt.Errorf("node(%s) is existed!", node.Name) 32 | } 33 | 34 | if _, err := ormer.Insert(&node); err != nil { 35 | return fmt.Errorf("database error, %s", err.Error()) 36 | } 37 | return nil 38 | } 39 | 40 | func UpdateNode(node models.ZcloudNode) error { 41 | ormer := GetOrmer() 42 | if !ormer.QueryTable("zcloud_node"). 43 | Filter("cluster", node.Cluster). 44 | Filter("name", node.Name).Exist() { 45 | return fmt.Errorf("node(%s) is not existed!", node.Name) 46 | } 47 | 48 | _, err := ormer.Update(&node) 49 | 50 | return err 51 | } 52 | 53 | func DeleteNode(cluster, name string) error { 54 | _, err := GetOrmer().Raw("delete from zcloud_node where cluster=? AND name=?", cluster, name).Exec() 55 | return err 56 | } 57 | 58 | func DeleteNodesByClusterName(clusterId string) error { 59 | _, err := GetOrmer().Raw("delete from zcloud_node where cluster=?", clusterId).Exec() 60 | return err 61 | } 62 | 63 | func GetNodeList(cluster string, filterQuery *utils.FilterQuery) (*utils.QueryResult, error) { 64 | var nodes []*models.ZcloudNode 65 | queryCond := orm.NewCondition() 66 | if cluster != "" { 67 | queryCond = queryCond.And("cluster", cluster) 68 | } 69 | if filterQuery != nil { 70 | filterCond := filterQuery.FilterCondition(nodeEnableFilterKeys) 71 | if filterCond != nil { 72 | queryCond = queryCond.AndCond(filterCond) 73 | } 74 | } 75 | 76 | query := GetOrmer().QueryTable("zcloud_node").OrderBy("-create_at").SetCond(queryCond) 77 | 78 | count, err := query.Count() 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | if filterQuery != nil && filterQuery.PageSize != 0 && filterQuery.PageIndex > 0 { 84 | query = query.Limit(filterQuery.PageSize, filterQuery.PageSize*(filterQuery.PageIndex-1)) 85 | } 86 | if _, err := query.All(&nodes); err != nil { 87 | return nil, err 88 | } 89 | return &utils.QueryResult{ 90 | Base: utils.PageInfo{ 91 | TotalNum: count, 92 | PageIndex: filterQuery.PageIndex, 93 | PageSize: filterQuery.PageSize, 94 | }, 95 | List: nodes}, err 96 | } 97 | func GetNodeByName(cluster, name string) (*models.ZcloudNode, error) { 98 | var node models.ZcloudNode 99 | if err := GetOrmer().QueryTable("zcloud_node"). 100 | Filter("cluster", cluster). 101 | Filter("name", name).One(&node); err != nil { 102 | if err == orm.ErrMultiRows { 103 | return nil, fmt.Errorf("node %s has multi rows", name) 104 | } 105 | return nil, err 106 | } 107 | 108 | return &node, nil 109 | } 110 | 111 | func GetNodeByIP(cluster, nodeIP string) (*models.ZcloudNode, error) { 112 | var node models.ZcloudNode 113 | if err := GetOrmer().QueryTable("zcloud_node"). 114 | Filter("cluster", cluster). 115 | Filter("ip", nodeIP).One(&node); err != nil { 116 | if err == orm.ErrMultiRows { 117 | return nil, fmt.Errorf("node %s has multi rows", nodeIP) 118 | } 119 | if err == orm.ErrNoRows { 120 | return nil, fmt.Errorf("node %s no row found", nodeIP) 121 | } 122 | return nil, err 123 | } 124 | 125 | return &node, nil 126 | } 127 | 128 | func GetNodeListByStatus(cluster string, department string, status string) ([]*models.ZcloudNode, error) { 129 | qs := GetOrmer().QueryTable("zcloud_node") 130 | if cluster != "" { 131 | qs = qs.Filter("cluster", cluster) 132 | } 133 | if department != "all" { 134 | qs = qs.Filter("department__in", department) 135 | } 136 | if status != "all" { 137 | qs = qs.Filter("status", status) 138 | } 139 | var nodes []*models.ZcloudNode 140 | if _, err := qs.All(&nodes); err != nil { 141 | return nil, err 142 | } 143 | return nodes, nil 144 | } 145 | -------------------------------------------------------------------------------- /backend/dao/template.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | models "kubecloud/backend/models" 6 | 7 | "kubecloud/common/utils" 8 | 9 | "github.com/astaxie/beego/orm" 10 | ) 11 | 12 | type TemplateModel struct { 13 | tOrmer orm.Ormer 14 | TableName string 15 | } 16 | 17 | var templateEnableFilterKeys = map[string]interface{}{ 18 | "namespace": nil, 19 | "name": nil, 20 | "description": nil, 21 | "creator": nil, 22 | "create_at": nil, 23 | } 24 | 25 | func NewTemplateModel() *TemplateModel { 26 | return &TemplateModel{ 27 | tOrmer: GetOrmer(), 28 | TableName: (&models.ZcloudTemplate{}).TableName(), 29 | } 30 | } 31 | 32 | func (tm *TemplateModel) CreateTemplate(template models.ZcloudTemplate) (*models.ZcloudTemplate, error) { 33 | template.Addons = models.NewAddons() 34 | _, err := tm.tOrmer.Insert(&template) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return tm.GetTemplate(template.Namespace, template.Name) 40 | } 41 | 42 | func (tm *TemplateModel) UpdateTemplate(template models.ZcloudTemplate) error { 43 | template.Addons = template.Addons.UpdateAddons() 44 | _, err := tm.tOrmer.Update(&template) 45 | 46 | return err 47 | } 48 | 49 | func (tm *TemplateModel) DeleteTemplate(namespace, name string) error { 50 | sql := "update " + tm.TableName + " set deleted=1, delete_at=now() where namespace=? and name=? and deleted=0" 51 | _, err := tm.tOrmer.Raw(sql, namespace, name).Exec() 52 | 53 | return err 54 | } 55 | 56 | func (tm *TemplateModel) GetTemplate(namespace, name string) (*models.ZcloudTemplate, error) { 57 | var template models.ZcloudTemplate 58 | 59 | if err := tm.tOrmer.QueryTable(tm.TableName). 60 | Filter("namespace", namespace). 61 | Filter("name", name). 62 | Filter("deleted", 0).One(&template); err != nil { 63 | return nil, err 64 | } 65 | 66 | return &template, nil 67 | } 68 | 69 | func (tm *TemplateModel) GetTemplateByID(templateId int64) (*models.ZcloudTemplate, error) { 70 | var template models.ZcloudTemplate 71 | 72 | if err := tm.tOrmer.QueryTable(tm.TableName). 73 | Filter("id", templateId). 74 | Filter("deleted", 0).One(&template); err != nil { 75 | return nil, err 76 | } 77 | 78 | return &template, nil 79 | } 80 | 81 | func (tm *TemplateModel) GetTemplateList(nslist []string, filterQuery *utils.FilterQuery) (*utils.QueryResult, error) { 82 | tList := []models.ZcloudTemplate{} 83 | 84 | query := tm.tOrmer.QueryTable(tm.TableName). 85 | Filter("deleted", 0).OrderBy("-create_at") 86 | if len(nslist) >= 1 { 87 | query = query.Filter("namespace__in", nslist) 88 | } else { 89 | return nil, fmt.Errorf("namespace must be given!") 90 | } 91 | if filterQuery.FilterVal != "" { 92 | if _, exist := templateEnableFilterKeys[filterQuery.FilterKey]; exist { 93 | query = query.Filter(filterQuery.FilterKey+"__icontains", filterQuery.FilterVal) 94 | } 95 | } 96 | if filterQuery.PageSize != 0 { 97 | realIndex := 0 98 | if filterQuery.PageIndex > 0 { 99 | realIndex = filterQuery.PageIndex - 1 100 | } 101 | query = query.Limit(filterQuery.PageSize, filterQuery.PageSize*realIndex) 102 | } 103 | if _, err := query.All(&tList); err != nil { 104 | return nil, err 105 | } 106 | count, err := query.Count() 107 | if err != nil { 108 | return nil, err 109 | } 110 | return &utils.QueryResult{ 111 | Base: utils.PageInfo{ 112 | TotalNum: count, 113 | PageIndex: filterQuery.PageIndex, 114 | PageSize: filterQuery.PageSize, 115 | }, 116 | List: tList}, err 117 | } 118 | -------------------------------------------------------------------------------- /backend/models/application.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type ZcloudApplication struct { 4 | Id int64 `orm:"pk;column(id);auto" json:"id"` 5 | Name string `orm:"column(name)" json:"name"` 6 | Version string `orm:"column(version)" json:"version"` 7 | // pod_version is deployment version: deployment_name=app_name+pod_version, if pod_version is not empty 8 | // or version when pod_version is empty 9 | PodVersion string `orm:"column(pod_version)" json:"pod_version"` 10 | BaseName string `orm:"column(base_name)" json:"base_name"` //saas name 11 | Env string `orm:"column(env)" json:"env"` 12 | DomainName string `orm:"column(domain_name)" json:"domain_name"` 13 | Kind string `orm:"column(kind)" json:"kind"` 14 | TemplateName string `orm:"column(template_name)" json:"template_name"` 15 | Cluster string `orm:"column(cluster)" json:"cluster"` 16 | Namespace string `orm:"column(namespace)" json:"namespace"` 17 | Replicas int `orm:"column(replicas)" json:"replicas"` 18 | Image string `orm:"column(image)" json:"image"` 19 | Template string `orm:"column(template);type(text)" json:"template,omitempty"` 20 | LabelSelector string `orm:"column(label_selector)" json:"label_selector,omitempty"` 21 | InjectServiceMesh string `orm:"column(inject_service_mesh)" json:"inject_service_mesh,omitempty"` 22 | StatusReplicas int32 `orm:"column(status_replicas)" json:"status_replicas"` 23 | ReadyReplicas int32 `orm:"column(ready_replicas)" json:"ready_replicas"` 24 | UpdatedReplicas int32 `orm:"column(updated_replicas)" json:"updated_replicas"` 25 | AvailableReplicas int32 `orm:"column(available_replicas)" json:"available_replicas"` 26 | AvailableStatus string `orm:"column(available_status)" json:"available_status"` 27 | Message string `orm:"column(message)" json:"message"` 28 | DeployStatus string `orm:"column(deploy_status)" json:"deploy_status"` 29 | DefaultDomainAddr string `orm:"column(default_domain_addr)" json:"default_domain_addr"` 30 | Labels string `orm:"column(labels);type(text)" json:"labels"` 31 | Addons 32 | } 33 | 34 | func (t *ZcloudApplication) TableName() string { 35 | return "zcloud_application" 36 | } 37 | -------------------------------------------------------------------------------- /backend/models/appversion.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type ZcloudVersion struct { 4 | Id int64 `orm:"pk;column(id);auto" json:"id"` 5 | Cluster string `orm:"column(cluster)" json:"cluster"` 6 | Namespace string `orm:"column(namespace)" json:"namespace"` 7 | Name string `orm:"column(name)" json:"name"` // name is application name// name is application name 8 | Kind string `orm:"column(kind)" json:"kind"` 9 | Version string `orm:"column(version)" json:"version"` 10 | PodVersion string `orm:"column(pod_version)" json:"pod_version"` 11 | Weight int `orm:"column(weight)" json:"weight"` 12 | Stage string `orm:"column(stage)" json:"stage"` // new or normal 13 | Replicas int `orm:"column(replicas)" json:"replicas"` 14 | CurReplicas int `orm:"column(cur_replicas)" json:"cur_replicas"` 15 | TemplateName string `orm:"column(template_name)" json:"template_name"` 16 | Image string `orm:"column(image)" json:"image"` 17 | Template string `orm:"column(template);type(text)" json:"template,omitempty"` 18 | InjectServiceMesh string `orm:"column(inject_service_mesh)" json:"inject_service_mesh,omitempty"` 19 | Addons 20 | } 21 | 22 | const ( 23 | STAGE_NEW = "new" 24 | STAGE_NORMAL = "normal" 25 | MIN_WEIGHT = 0 26 | DEF_INIT_WEIGHT = 10 27 | MAX_WEIGHT = 100 28 | DEFAULT_WEIGHT = 1 29 | INIT_CUR_REPLICAS = 1 30 | ) 31 | 32 | func (t *ZcloudVersion) TableName() string { 33 | return "zcloud_version" 34 | } 35 | -------------------------------------------------------------------------------- /backend/models/cluster.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | const ( 4 | ClusterStatusToBeConfigured string = "ToBeConfigured" 5 | ClusterStatusPending string = "Pending" 6 | ClusterStatusRunning string = "Running" 7 | ClusterStatusError string = "Error" 8 | ClusterStatusUpdating string = "Updating" 9 | ) 10 | 11 | type ZcloudCluster struct { 12 | Id int64 `orm:"pk;column(id);auto" json:"id"` 13 | Name string `orm:"column(name)" json:"name"` 14 | ClusterId string `orm:"column(cluster_id)" json:"cluster_id"` 15 | Tenant string `orm:"column(tenant)" json:"tenant"` 16 | Env string `orm:"column(env)" json:"env"` 17 | Usage string `orm:"column(usage)" json:"usage"` 18 | DisplayName string `orm:"column(display_name)" json:"display_name"` 19 | Registry string `orm:"column(registry)" json:"registry"` 20 | RegistryName string `orm:"column(registry_name)" json:"registry_name"` 21 | ImagePullAddr string `orm:"column(image_pull_addr)" json:"image_pull_addr"` 22 | DockerVersion string `orm:"column(docker_version)" json:"docker_version"` 23 | NetworkPlugin string `orm:"column(network_plugin)" json:"network_plugin"` 24 | DomainSuffix string `orm:"column(domain_suffix)" json:"domain_suffix"` 25 | Certificate string `orm:"column(certificate);type(text)" json:"certificate"` 26 | PrometheusAddr string `orm:"column(prometheus_addr)" json:"prometheus_addr"` 27 | Status string `orm:"column(status)" json:"status"` 28 | KubeVersion string `orm:"column(kube_version)" json:"kube_version"` 29 | LoadbalancerDomainName string `orm:"column(lb_name)" json:"loadbalancer_domain_name"` 30 | LoadbalancerIP string `orm:"column(lb_ip)" json:"loadbalancer_ip"` 31 | LoadbalancerPort string `orm:"column(lb_port)" json:"loadbalancer_port"` 32 | KubeServiceAddress string `orm:"column(kube_service_addr)" json:"kube_service_address"` 33 | KubePodSubnet string `orm:"column(kube_pods_subnet)" json:"kube_pod_subnet"` 34 | TillerHost string `orm:"column(tiller_host)" json:"tiller_host"` 35 | PromRuleIndex int64 `orm:"column(prom_rule_index);default(0)" json:"prom_rule_index"` 36 | IngressSLB string `orm:"column(ingress_slb)" json:"ingress_slb"` 37 | LabelPrefix string `orm:"column(label_prefix)" json:"label_prefix"` 38 | ConfigRepo string `orm:"column(config_repo)" json:"config_repo"` 39 | ConfigRepoBranch string `orm:"column(config_repo_branch)" json:"config_repo_branch"` 40 | ConfigRepoToken string `orm:"column(config_repo_token)" json:"config_repo_token"` 41 | LastCommitId string `orm:"column(last_commit_id)" json:"last_commit_id"` 42 | Addons 43 | } 44 | 45 | func (t *ZcloudCluster) TableName() string { 46 | return "zcloud_cluster" 47 | } 48 | 49 | type ZcloudClusterDomainSuffix struct { 50 | Id int64 `orm:"pk;column(id);auto" json:"id"` 51 | Cluster string `orm:"column(cluster)" json:"cluster"` 52 | DomainSuffix string `orm:"column(domain_suffix)" json:"domain_suffix"` 53 | IsDefault bool `orm:"column(is_default)" json:"is_default"` 54 | Addons 55 | } 56 | 57 | func (t *ZcloudClusterDomainSuffix) TableName() string { 58 | return "zcloud_cluster_domain_suffix" 59 | } 60 | -------------------------------------------------------------------------------- /backend/models/common.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "kubecloud/backend/service" 9 | 10 | "github.com/astaxie/beego/orm" 11 | "github.com/go-sql-driver/mysql" 12 | ) 13 | 14 | // NOTE: should perfer AddonsUnix, Addons will be deprecated in future 15 | 16 | // AddonsUnix ... 17 | type AddonsUnix struct { 18 | Deleted int8 `orm:"column(deleted)" json:"deleted"` 19 | CreatedAt int64 `orm:"column(created_at)" json:"created_at"` 20 | UpdatedAt int64 `orm:"column(updated_at)" json:"updated_at"` 21 | DeletedAt *int64 `orm:"column(deleted_at);null" json:"deleted_at"` 22 | } 23 | 24 | // NewAddonsUnix ... 25 | func NewAddonsUnix() AddonsUnix { 26 | now := time.Now().Unix() 27 | return AddonsUnix{ 28 | Deleted: 0, 29 | CreatedAt: now, 30 | UpdatedAt: now, 31 | DeletedAt: nil, 32 | } 33 | } 34 | 35 | // MarkUpdated ... 36 | func (a *AddonsUnix) MarkUpdated() { 37 | now := time.Now().Unix() 38 | a.UpdatedAt = now 39 | } 40 | 41 | // MarkDeleted ... 42 | func (a *AddonsUnix) MarkDeleted() { 43 | now := time.Now().Unix() 44 | a.DeletedAt = &now 45 | a.Deleted = 1 46 | } 47 | 48 | type Addons struct { 49 | Deleted int8 `orm:"column(deleted)" json:"deleted"` 50 | CreateAt time.Time `orm:"column(create_at)" json:"create_at"` 51 | UpdateAt time.Time `orm:"column(update_at)" json:"update_at"` 52 | DeleteAt time.Time `orm:"column(delete_at);null" json:"delete_at"` 53 | } 54 | 55 | func NewAddonsWithCreationTimestamp(timestamp time.Time) Addons { 56 | timeNow := timestamp 57 | timeDel, _ := time.Parse("2006-01-02 15:04:05", time.Unix(0, 0).Local().Format("2006-01-02 15:04:05")) 58 | return Addons{ 59 | Deleted: 0, 60 | CreateAt: timeNow, 61 | UpdateAt: timeNow, 62 | DeleteAt: timeDel, 63 | } 64 | } 65 | 66 | func NewAddons() Addons { 67 | timeNow, _ := time.Parse("2006-01-02 15:04:05", time.Now().Local().Format("2006-01-02 15:04:05")) 68 | timeDel, _ := time.Parse("2006-01-02 15:04:05", time.Unix(0, 0).Local().Format("2006-01-02 15:04:05")) 69 | return Addons{ 70 | Deleted: 0, 71 | CreateAt: timeNow, 72 | UpdateAt: timeNow, 73 | DeleteAt: timeDel, 74 | } 75 | } 76 | 77 | func (ons Addons) UpdateAddons() Addons { 78 | ons.CreateAt, _ = time.Parse("2006-01-02 15:04:05", ons.CreateAt.Format("2006-01-02 15:04:05")) 79 | ons.UpdateAt, _ = time.Parse("2006-01-02 15:04:05", time.Now().Local().Format("2006-01-02 15:04:05")) 80 | return ons 81 | } 82 | 83 | func (ons Addons) FormatAddons() Addons { 84 | ons.CreateAt, _ = time.Parse("2006-01-02 15:04:05", ons.CreateAt.Format("2006-01-02 15:04:05")) 85 | ons.UpdateAt, _ = time.Parse("2006-01-02 15:04:05", ons.UpdateAt.Format("2006-01-02 15:04:05")) 86 | return ons 87 | } 88 | 89 | type HardAddons struct { 90 | CreateAt time.Time `orm:"column(create_at)" json:"create_at"` 91 | UpdateAt time.Time `orm:"column(update_at)" json:"update_at"` 92 | } 93 | 94 | func NewHardAddons() HardAddons { 95 | timeNow, _ := time.Parse("2006-01-02 15:04:05", time.Now().Local().Format("2006-01-02 15:04:05")) 96 | return HardAddons{ 97 | CreateAt: timeNow, 98 | UpdateAt: timeNow, 99 | } 100 | } 101 | 102 | var ( 103 | dbName string 104 | tableNames []string 105 | ) 106 | 107 | func initOrm() { 108 | config := service.GetAppConfig() 109 | DatabaseDebug, _ := config.Bool("DB::databaseDebug") 110 | DefaultRowsLimit, _ := config.Int("DB::defaultRowsLimit") 111 | DatabaseUrl := config.String("DB::databaseUrl") 112 | 113 | if cfg, err := mysql.ParseDSN(DatabaseUrl); err == nil { 114 | dbName = cfg.DBName 115 | } 116 | 117 | orm.Debug = DatabaseDebug 118 | if DefaultRowsLimit != 0 { 119 | orm.DefaultRowsLimit = DefaultRowsLimit 120 | } 121 | 122 | if err := orm.RegisterDriver("mysql", orm.DRMySQL); err != nil { 123 | panic(fmt.Sprintf(`failed to register driver, error: "%s"`, err.Error())) 124 | } 125 | if err := orm.RegisterDataBase("default", "mysql", DatabaseUrl); err != nil { 126 | panic(fmt.Sprintf(`failed to register database, error: "%s", url: "%s"`, err.Error(), DatabaseUrl)) 127 | } 128 | registerModel := func(models ...interface{}) { 129 | tableNames = make([]string, len(models)) 130 | for i, model := range models { 131 | obj := model.(interface { 132 | TableName() string 133 | }) 134 | tableNames[i] = obj.TableName() 135 | } 136 | orm.RegisterModel(models...) 137 | } 138 | registerModel( 139 | new(ZcloudCluster), 140 | new(ZcloudNode), 141 | new(ZcloudHarbor), 142 | new(ZcloudHarborProject), 143 | new(ZcloudHarborRepository), 144 | new(K8sSecret), 145 | new(ZcloudEvent), 146 | new(ZcloudTemplate), 147 | new(ZcloudApplication), 148 | new(ZcloudVersion), 149 | new(K8sIngress), 150 | new(K8sIngressRule), 151 | new(K8sService), 152 | new(K8sServicePort), 153 | new(K8sEndpoint), 154 | new(K8sEndpointAddress), 155 | new(K8sNamespace), 156 | new(ZcloudRepositoryTag), 157 | new(ZcloudClusterDomainSuffix), 158 | ) 159 | } 160 | 161 | func Init() { 162 | initOrm() 163 | orm.RunSyncdb("default", false, true) 164 | } 165 | 166 | func InitMock() { 167 | initOrm() 168 | 169 | // check database name and clear all tables 170 | if !strings.Contains(dbName, "test") { 171 | panic(fmt.Sprintf(`invalid database: "%v"`, dbName)) 172 | } 173 | 174 | orm.RunSyncdb("default", false, true) 175 | 176 | ormer := orm.NewOrm() 177 | for _, name := range tableNames { 178 | if _, err := ormer.Raw(fmt.Sprintf("truncate table %v", name)).Exec(); err != nil { 179 | panic(err) 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /backend/models/event.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type ZcloudEvent struct { 8 | ID int64 `orm:"pk;column(id);auto" json:"id"` 9 | EventUid string `orm:"column(event_uid);size(36)" json:"event_uid"` 10 | ActionType string `orm:"column(action_type);size(10)" json:"action_type"` 11 | EventType string `orm:"column(event_type);size(10)" json:"event_type"` 12 | Cluster string `orm:"column(cluster)" json:"cluster"` 13 | Namespace string `orm:"column(namespace);size(100)" json:"namespace"` 14 | SourceComponent string `orm:"column(source_component);size(50)" json:"source_component"` 15 | SourceHost string `orm:"column(source_host);size(20)" json:"source_host"` 16 | ObjectKind string `orm:"column(object_kind);size(20)" json:"object_kind"` 17 | ObjectName string `orm:"column(object_name);size(100)" json:"object_name"` 18 | ObjectUid string `orm:"column(object_uid);size(36)" json:"object_uid"` 19 | FieldPath string `orm:"column(field_path);size(200)" json:"field_path"` 20 | Reason string `orm:"column(reason);size(100)" json:"reason"` 21 | Message string `orm:"column(message);type(text)" json:"message"` 22 | Count int32 `orm:"column(count)" json:"count"` 23 | FirstTimestamp time.Time `orm:"column(first_time)" json:"first_time"` 24 | LastTimestamp time.Time `orm:"column(last_time);index" json:"last_time"` 25 | } 26 | 27 | func (t *ZcloudEvent) TableName() string { 28 | return "zcloud_event" 29 | } 30 | -------------------------------------------------------------------------------- /backend/models/harbor.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | validate "kubecloud/common/validate" 8 | ) 9 | 10 | type ZcloudHarbor struct { 11 | ID int64 `orm:"pk;column(id);auto" json:"id"` 12 | HarborId string `orm:"column(harbor_id)" json:"harbor_id"` 13 | Tenant string `orm:"column(tenant)" json:"-"` 14 | HarborName string `orm:"column(harbor_name);size(100)" json:"harbor_name"` 15 | HarborAddr string `orm:"column(harbor_addr);size(255)" json:"harbor_addr"` 16 | HarborDesc string `orm:"column(harbor_desc);size(255)" json:"harbor_desc"` 17 | HarborUser string `orm:"column(harbor_user);size(255)" json:"harbor_user"` 18 | HarborPassword string `orm:"column(harbor_password);size(255)" json:"harbor_password"` 19 | HarborAuth string `orm:"column(harbor_auth);size(255)" json:"-"` 20 | HarborHTTPSMode bool `orm:"column(harbor_https_mode);size(255)" json:"harbor_https_mode"` 21 | HarborVersion string `orm:"column(harbor_version);size(255)" json:"harbor_version"` 22 | Addons 23 | } 24 | 25 | func (t *ZcloudHarbor) TableName() string { 26 | return "zcloud_harbor" 27 | } 28 | 29 | type HarborReq struct { 30 | Tenant string `json:"tenant"` 31 | HarborId string `json:"harbor_id"` 32 | HarborName string `json:"harbor_name"` 33 | HarborDesc string `json:"harbor_desc"` 34 | HarborAddr string `json:"harbor_addr"` 35 | HarborUser string `json:"harbor_user"` 36 | HarborPassword string `json:"harbor_password"` 37 | HarborHTTPSMode bool `json:"harbor_https_mode"` 38 | HarborVersion string `json:"harbor_version"` 39 | } 40 | 41 | func (hr *HarborReq) Verify(harborReq *HarborReq) error { 42 | if err := validate.ValidateString(harborReq.HarborName); err != nil { 43 | return fmt.Errorf("harbor_name erro: %v", err.Error()) 44 | } 45 | if err := validate.ValidateString(harborReq.Tenant); err != nil { 46 | return fmt.Errorf("harbor's tenant is not right: %v", err.Error()) 47 | } 48 | return nil 49 | } 50 | 51 | type ZcloudHarborProject struct { 52 | ID int64 `orm:"pk;column(id);auto" json:"id"` 53 | Harbor string `orm:"column(harbor)" json:"harbor"` 54 | ProjectID int `orm:"column(project_id)" json:"project_id"` 55 | ProjectName string `orm:"column(project_name)" json:"project_name"` 56 | ProjectPublic int `orm:"column(project_public)" json:"project_public"` 57 | RepoCount int `orm:"column(repo_count)" json:"repo_count"` 58 | } 59 | 60 | func (t *ZcloudHarborProject) TableName() string { 61 | return "zcloud_harbor_project" 62 | } 63 | 64 | func (u *ZcloudHarborProject) TableUnique() [][]string { 65 | return [][]string{ 66 | []string{"Harbor", "ProjectID", "ProjectName"}, 67 | } 68 | } 69 | 70 | type ZcloudHarborRepository struct { 71 | ID int64 `orm:"pk;column(id);auto" json:"id"` 72 | Harbor string `orm:"column(harbor)" json:"harbor"` 73 | ProjectID int `orm:"column(project_id)" json:"project_id"` 74 | ProjectName string `orm:"column(project_name)" json:"project_name"` 75 | ProjectPublic int `orm:"column(project_public)" json:"project_public"` 76 | RepositoryName string `orm:"column(repository_name)" json:"repository_name"` 77 | PullCount int `orm:"column(pull_count)" json:"pull_count"` 78 | TagsCount int `orm:"column(tags_count)" json:"tags_count"` 79 | } 80 | 81 | func (t *ZcloudHarborRepository) TableName() string { 82 | return "zcloud_harbor_repository" 83 | } 84 | 85 | func (u *ZcloudHarborRepository) TableUnique() [][]string { 86 | return [][]string{ 87 | []string{"Harbor", "ProjectID", "ProjectName", "RepositoryName"}, 88 | } 89 | } 90 | 91 | type ZcloudRepositoryTag struct { 92 | ID int64 `orm:"pk;column(id);auto" json:"id"` 93 | Harbor string `orm:"column(harbor)" json:"harbor"` 94 | RepositoryName string `orm:"column(repository_name)" json:"repository_name"` 95 | Tag string `orm:"column(tag)" json:"tag"` 96 | CodeBranch string `orm:"column(code_branch)" json:"code_branch"` 97 | Labels string `orm:"column(labels)" json:"labels"` 98 | CreateAt string `orm:"column(create_at)" json:"create_at"` 99 | } 100 | 101 | func (tag *ZcloudRepositoryTag) TableName() string { 102 | return "zcloud_repository_tag" 103 | } 104 | 105 | func (u *ZcloudRepositoryTag) TableUnique() [][]string { 106 | return [][]string{ 107 | []string{"Harbor", "RepositoryName", "Tag"}, 108 | } 109 | } 110 | 111 | type RepositoryTagSortBy func(o1, o2 *ZcloudRepositoryTag) bool 112 | 113 | func (by RepositoryTagSortBy) Sort(list []*ZcloudRepositoryTag) { 114 | ts := &repositoryTagSorter{ 115 | Objects: list, 116 | by: by, 117 | } 118 | sort.Sort(ts) 119 | } 120 | 121 | type repositoryTagSorter struct { 122 | Objects []*ZcloudRepositoryTag 123 | by func(o1, o2 *ZcloudRepositoryTag) bool 124 | } 125 | 126 | func (s *repositoryTagSorter) Len() int { 127 | return len(s.Objects) 128 | } 129 | 130 | func (s *repositoryTagSorter) Swap(i, j int) { 131 | s.Objects[i], s.Objects[j] = s.Objects[j], s.Objects[i] 132 | } 133 | 134 | func (s *repositoryTagSorter) Less(i, j int) bool { 135 | return s.by(s.Objects[i], s.Objects[j]) 136 | } 137 | -------------------------------------------------------------------------------- /backend/models/k8s_endpoint.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type K8sEndpoint struct { 4 | Id int64 `orm:"pk;column(id);auto"` 5 | Name string `orm:"column(name)"` 6 | Cluster string `orm:"column(cluster)"` 7 | Namespace string `orm:"column(namespace)"` 8 | OwnerName string `orm:"column(owner_name)"` 9 | // port is unique: port is service port's target_port 10 | Port int32 `orm:"column(port)"` 11 | PortName string `orm:"column(port_name)"` 12 | Protocol string `orm:"column(protocol)"` 13 | Addresses []*K8sEndpointAddress `orm:"reverse(many);column(addresses)"` //设置一对多关系 14 | Addons 15 | } 16 | 17 | func (t *K8sEndpoint) TableName() string { 18 | return "k8s_endpoint" 19 | } 20 | 21 | func (u *K8sEndpoint) TableUnique() [][]string { 22 | return [][]string{ 23 | []string{"Cluster", "Namespace", "Name"}, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/models/k8s_endpoint_address.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type K8sEndpointAddress struct { 4 | Id int64 `orm:"pk;column(id);auto"` 5 | Cluster string `orm:"column(cluster)"` 6 | Namespace string `orm:"column(namespace)"` 7 | EndpointName string `orm:"column(endpoint_name)"` 8 | IP string `orm:"column(ip)"` 9 | NodeName string `orm:"column(node_name)"` 10 | TargetRefName string `orm:"column(target_ref_name)"` 11 | Endpoint *K8sEndpoint `orm:"rel(fk);column(endpoint)"` 12 | Addons 13 | } 14 | 15 | func (t *K8sEndpointAddress) TableName() string { 16 | return "k8s_endpoint_address" 17 | } 18 | -------------------------------------------------------------------------------- /backend/models/k8s_ingress.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type K8sIngress struct { 4 | Id int64 `orm:"pk;column(id);auto"` 5 | // name is unique 6 | Name string `orm:"column(name)"` 7 | Namespace string `orm:"column(namespace)"` 8 | Cluster string `orm:"column(cluster)"` 9 | Rules []*K8sIngressRule `orm:"reverse(many);column(rules)"` 10 | Annotation string `orm:"column(annotation);type(text)"` 11 | Addons 12 | } 13 | 14 | func (t *K8sIngress) TableName() string { 15 | return "k8s_ingress" 16 | } 17 | 18 | func (u *K8sIngress) TableUnique() [][]string { 19 | return [][]string{ 20 | []string{"Cluster", "Namespace", "Name"}, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/models/k8s_ingress_rule.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type K8sIngressRule struct { 4 | Id int64 `orm:"pk;column(id);auto" json:"id"` 5 | Cluster string `orm:"column(cluster)" json:"cluster"` 6 | Namespace string `orm:"column(namespace)" json:"namespace"` 7 | IngressName string `orm:"column(ingress_name)" json:"ingress_name,omitempty"` 8 | // host is unique 9 | Host string `orm:"column(host)" json:"host,omitempty"` 10 | Path string `orm:"column(path)" json:"path,omitempty"` 11 | SecretName string `orm:"column(secret_name)" json:"secret_name"` 12 | IsTls bool `orm:"column(is_tls)" json:"is_tls,omitempty"` 13 | ServiceName string `orm:"column(service_name)" json:"service_name"` 14 | ServicePort int `orm:"column(service_port)" json:"service_port"` 15 | Ingress *K8sIngress `orm:"rel(fk);column(ingress)" json:"-"` 16 | Addons 17 | } 18 | 19 | func (t *K8sIngressRule) TableName() string { 20 | return "k8s_ingress_rule" 21 | } 22 | 23 | func (u *K8sIngressRule) TableUnique() [][]string { 24 | return [][]string{ 25 | []string{"Cluster", "Namespace", "IngressName"}, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/models/k8s_namespace.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type K8sNamespace struct { 4 | ID int64 `orm:"pk;column(id);auto" json:"id"` 5 | Cluster string `orm:"column(cluster);index" json:"cluster"` 6 | Name string `orm:"column(name);index" json:"name"` 7 | Desc string `orm:"column(desc)" json:"desc"` 8 | CPUQuota string `orm:"column(cpu_quota)" json:"cpu_quota"` 9 | MemoryQuota string `orm:"column(memory_quota)" json:"memory_quota"` 10 | AddonsUnix 11 | } 12 | 13 | func (t *K8sNamespace) TableName() string { 14 | return "k8s_namespace" 15 | } 16 | -------------------------------------------------------------------------------- /backend/models/k8s_secret.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type K8sSecret struct { 4 | Id int64 `orm:"pk;column(id);auto" json:"id"` 5 | Name string `orm:"column(name)" json:"name"` 6 | Cluster string `orm:"column(cluster)" json:"cluster"` 7 | OwnerName string `orm:"column(owner_name)"` 8 | Namespace string `orm:"column(namespace)" json:"namespace"` 9 | Description string `orm:"column(description)" json:"description,omitempty"` 10 | Type string `orm:"column(type)" json:"type,omitempty"` 11 | Data string `orm:"column(data);type(text)" json:"data,omitempty"` 12 | Addons 13 | } 14 | 15 | func (t *K8sSecret) TableName() string { 16 | return "k8s_secret" 17 | } 18 | 19 | func (u *K8sSecret) TableUnique() [][]string { 20 | return [][]string{ 21 | []string{"Cluster", "Namespace", "Name"}, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/models/k8s_service.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type K8sService struct { 4 | Id int64 `orm:"pk;column(id);auto"` 5 | Name string `orm:"column(name)"` 6 | Cluster string `orm:"column(cluster)"` 7 | Namespace string `orm:"column(namespace)"` 8 | OwnerName string `orm:"column(owner_name)"` 9 | Type string `orm:"column(type)"` 10 | ClusterIP string `orm:"column(cluster_ip)"` 11 | Ports []*K8sServicePort `orm:"reverse(many);column(ports)"` 12 | Annotation string `orm:"column(annotation);type(text)"` 13 | Addons 14 | } 15 | 16 | func (t *K8sService) TableName() string { 17 | return "k8s_service" 18 | } 19 | 20 | func (u *K8sService) TableUnique() [][]string { 21 | return [][]string{ 22 | []string{"Cluster", "Namespace", "Name"}, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /backend/models/k8s_service_port.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type K8sServicePort struct { 4 | Id int64 `orm:"pk;column(id);auto"` 5 | Name string `orm:"column(name)"` 6 | Cluster string `orm:"column(cluster)"` 7 | Namespace string `orm:"column(namespace)"` 8 | ServiceName string `orm:"column(service_name)"` 9 | Protocol string `orm:"column(protocol)"` 10 | Port int `orm:"column(port)"` 11 | TargetPort int `orm:"column(target_port)"` 12 | NodePort int `orm:"column(node_port)"` 13 | Service *K8sService `orm:"rel(fk);column(service)"` //设置一对多关系 14 | Addons 15 | } 16 | 17 | func (t *K8sServicePort) TableName() string { 18 | return "k8s_service_port" 19 | } 20 | 21 | func (u *K8sServicePort) TableUnique() [][]string { 22 | return [][]string{ 23 | []string{"Cluster", "Namespace", "ServiceName", "Port"}, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/models/node.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | const ( 4 | NodeStatusPending string = "Pending" 5 | NodeStatusRunning string = "Running" 6 | NodeStatusOffline string = "Offline" 7 | NodeStatusError string = "Error" 8 | ) 9 | 10 | type ZcloudNode struct { 11 | Id int64 `orm:"pk;column(id);auto" json:"id"` 12 | Name string `orm:"column(name)" json:"name"` 13 | Cluster string `orm:"column(cluster)" json:"cluster"` 14 | Department string `orm:"column(department)" json:"department"` 15 | BizCluster string `orm:"column(bizcluster)" json:"bizcluster"` 16 | IP string `orm:"column(ip)" json:"ip"` 17 | PodCidr string `orm:"column(pod_cidr)" json:"pod_cidr"` 18 | Labels string `orm:"column(labels);size(2048)" json:"labels"` 19 | CPU int64 `orm:"column(cpu)" json:"cpu"` 20 | CPURequests int64 `orm:"column(cpu_requests)" json:"cpu_requests"` 21 | CPULimits int64 `orm:"column(cpu_limits)" json:"cpu_limits"` 22 | Memory int64 `orm:"column(memory)" json:"memory"` 23 | MemoryRequests int64 `orm:"column(memory_requests)" json:"memory_requests"` 24 | MemoryLimits int64 `orm:"column(memory_limits)" json:"memory_limits"` 25 | Status string `orm:"column(status)" json:"status"` 26 | InitStatus string `orm:"column(init_status)" json:"init_status"` 27 | Creator string `orm:"column(creator)" json:"creator"` 28 | BizLabelStatus string `orm:"column(biz_label_status)" json:"biz_label_status"` 29 | DeployMode string `orm:"column(deploy_mode)" json:"deploy_mode"` 30 | DeployError string `orm:"column(deploy_err)" json:"deploy_err"` 31 | Monitor bool `orm:"column(monitor);default(false)" json:"monitor"` 32 | Influxdb bool `orm:"column(influxdb);default(false)" json:"influxdb"` 33 | Ingress bool `orm:"column(ingress);default(false)" json:"ingress"` 34 | External bool `orm:"column(external);default(false)" json:"external"` 35 | HardAddons 36 | } 37 | 38 | const ( 39 | BizLabelSetWaiting string = "waiting" 40 | BizLabelSetFinished string = "" 41 | ) 42 | 43 | func (u *ZcloudNode) TableUnique() [][]string { 44 | return [][]string{ 45 | []string{"Name", "Cluster"}, 46 | } 47 | } 48 | 49 | func (t *ZcloudNode) TableName() string { 50 | return "zcloud_node" 51 | } 52 | -------------------------------------------------------------------------------- /backend/models/template.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type ZcloudTemplate struct { 4 | Id int64 `orm:"pk;column(id);auto" json:"id"` 5 | Name string `orm:"column(name)" json:"name"` 6 | Namespace string `orm:"column(namespace)" json:"namespace"` 7 | Description string `orm:"column(description)" json:"description,omitempty"` 8 | Spec string `orm:"column(spec);type(text)" json:"spec"` //TemplateSpec 9 | Kind string `orm:"column(kind)" json:"kind"` 10 | Addons 11 | } 12 | 13 | func (t *ZcloudTemplate) TableName() string { 14 | return "zcloud_template" 15 | } 16 | -------------------------------------------------------------------------------- /backend/resource/apptemplate.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "sync" 8 | 9 | "kubecloud/backend/models" 10 | 11 | "github.com/astaxie/beego" 12 | yamlencoder "github.com/ghodss/yaml" 13 | ) 14 | 15 | const TemplateMaxSize = 1024000 //1000KB 16 | 17 | //single app template interface 18 | //simpleapp and nativeapp template support this interface 19 | type AppTemplate interface { 20 | GenerateAppObject(cluster, namespace, tplname, domainSuffix string) (*models.ZcloudApplication, error) 21 | UpdateAppObject(app *models.ZcloudApplication, domainSuffix string) error 22 | GenerateKubeObject(cluster, namespace, podVersion, domainSuffix string) (map[string]interface{}, error) 23 | GetAppName() string 24 | GetAppKind() string 25 | GetAppVersion() string 26 | String() (string, error) 27 | Image(param []ContainerParam) AppTemplate 28 | DefaultLabel() AppTemplate 29 | Replicas(replicas int) AppTemplate 30 | IsInjectServiceMesh() bool 31 | } 32 | 33 | type WorkerResult struct { 34 | AppName string 35 | AppVersion string 36 | AppKind string 37 | Result error 38 | } 39 | 40 | func CreateAppTemplateByApp(app models.ZcloudApplication) (AppTemplate, error) { 41 | return CreateNativeAppTemplate(app, "", nil) 42 | } 43 | 44 | func DeployAppTemplates(appTplList []AppTemplate, projectid int64, cluster, namespace, tname string, eparam *ExtensionParam) error { 45 | if len(appTplList) == 0 { 46 | return nil 47 | } 48 | errInfoList := []string{} 49 | var workers []*DeployWorker 50 | ar, err := NewAppRes(cluster, nil) 51 | if err != nil { 52 | return err 53 | } 54 | workerResult := make(chan WorkerResult) 55 | var wg sync.WaitGroup 56 | for _, tpl := range appTplList { 57 | wg.Add(1) 58 | wk := NewDeployWorker(tpl.GetAppName(), namespace, tpl.GetAppKind(), ar, eparam, tpl) 59 | workers = append(workers, wk) 60 | param := AppParam{Name: tpl.GetAppName()} 61 | go func(app AppTemplate) { 62 | defer wg.Done() 63 | err := wk.Start(tname, param) 64 | workerResult <- WorkerResult{ 65 | AppName: app.GetAppName(), 66 | AppVersion: app.GetAppVersion(), 67 | AppKind: app.GetAppKind(), 68 | Result: err, 69 | } 70 | }(tpl) 71 | } 72 | go func() { 73 | wg.Wait() 74 | close(workerResult) 75 | }() 76 | for res := range workerResult { 77 | if res.Result != nil { 78 | errInfoList = append(errInfoList, res.AppName+":"+res.Result.Error()) 79 | beego.Error(res.Result) 80 | } else { 81 | beego.Info("deploy application " + res.AppName + " successfully!") 82 | } 83 | } 84 | if len(errInfoList) != 0 { 85 | return fmt.Errorf(strings.Join(errInfoList, ";")) 86 | } 87 | 88 | return nil 89 | } 90 | 91 | func AppTemplateToYamlString(tpl AppTemplate, cluster, namespace, podVersion, domainSuffix string) (string, error) { 92 | objs, err := tpl.GenerateKubeObject(cluster, namespace, podVersion, domainSuffix) 93 | if err != nil && objs == nil { 94 | beego.Error("generate kubernetes object failed:", err) 95 | return "", err 96 | } 97 | ctx := []byte{} 98 | elems := []reflect.Value{} 99 | for _, obj := range objs { 100 | v := reflect.ValueOf(obj) 101 | switch v.Kind() { 102 | case reflect.Ptr: 103 | elems = append(elems, v) 104 | case reflect.Slice, reflect.Array: 105 | for i := 0; i < v.Len(); i++ { 106 | elems = append(elems, v.Index(i)) 107 | } 108 | default: 109 | beego.Debug("object kind:", v.Kind()) 110 | } 111 | } 112 | for _, elem := range elems { 113 | yamlBytes, err := yamlencoder.Marshal(elem.Interface()) 114 | if err != nil { 115 | beego.Error("yaml marshal object failed:", err) 116 | } 117 | ctx = append(ctx, yamlBytes...) 118 | ctx = append(ctx, []byte(YamlSeparator)...) 119 | } 120 | 121 | return strings.TrimSuffix(string(ctx), YamlSeparator), nil 122 | } 123 | -------------------------------------------------------------------------------- /backend/resource/configmap.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego" 6 | corev1 "k8s.io/api/core/v1" 7 | "k8s.io/apimachinery/pkg/api/errors" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "kubecloud/backend/util/labels" 10 | "sort" 11 | // "k8s.io/apimachinery/pkg/fields" 12 | 13 | "kubecloud/backend/service" 14 | "kubecloud/common" 15 | "kubecloud/common/keyword" 16 | "kubecloud/common/validate" 17 | "kubecloud/gitops" 18 | ) 19 | 20 | type ConfigMapVolume struct { 21 | Name string `json:"name,omitempty"` 22 | Items []string `json:"items,omitempty"` 23 | } 24 | 25 | type ConfigMaps []corev1.ConfigMap 26 | 27 | func (c ConfigMaps) Len() int { return len(c) } 28 | func (c ConfigMaps) Less(i, j int) bool { 29 | return c[i].ObjectMeta.CreationTimestamp.Time.After(c[j].ObjectMeta.CreationTimestamp.Time) 30 | } 31 | func (c ConfigMaps) Swap(i, j int) { c[i], c[j] = c[j], c[i] } 32 | 33 | func ConfigMapCreate(cluster string, configMap *corev1.ConfigMap) (*corev1.ConfigMap, error) { 34 | if ok, err := configMapValidator(configMap); !ok { 35 | return nil, err 36 | } 37 | go gitops.CommitK8sResource(cluster, []interface{}{configMap}) 38 | check := func(param interface{}) error { 39 | _, err := ConfigMapInspect(cluster, configMap.Namespace, configMap.Name) 40 | if errors.IsNotFound(err) { 41 | return fmt.Errorf("waiting to create......") 42 | } else { 43 | return nil 44 | } 45 | } 46 | result := make(chan error) 47 | go func() { 48 | result <- WaitSync(nil, SYNC_CHECK_STEP*10, SYNC_TIMEOUT*10, check) 49 | }() 50 | err := <-result 51 | return configMap, err 52 | } 53 | 54 | func ConfigMapList(cluster string, namespace string) ([]corev1.ConfigMap, error) { 55 | client, err := service.GetClientset(cluster) 56 | if err != nil { 57 | beego.Error(fmt.Sprintf("Get ConfigMap list error: %v", err.Error())) 58 | return nil, common.NewInternalServerError().SetCause(err) 59 | } 60 | if namespace == "all" { 61 | namespace = corev1.NamespaceAll 62 | } 63 | configMaps := ConfigMaps{} 64 | configMapList, err := client.CoreV1().ConfigMaps(namespace).List(metav1.ListOptions{}) 65 | if err != nil { 66 | beego.Error(fmt.Sprintf("Get ConfigMap list error: %v", err.Error())) 67 | return nil, common.NewInternalServerError().SetCause(err) 68 | } 69 | configMaps = configMapList.Items 70 | sort.Sort(configMaps) 71 | return configMaps, nil 72 | } 73 | 74 | func ConfigMapInspect(cluster, namespace, name string) (*corev1.ConfigMap, error) { 75 | client, err := service.GetClientset(cluster) 76 | if err != nil { 77 | beego.Error(fmt.Sprintf("Get ConfigMap inspect error: %v", err.Error())) 78 | return nil, err 79 | } 80 | configMap, err := client.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{}) 81 | if err != nil { 82 | beego.Error(fmt.Sprintf("Get ConfigMap inspect error: %v", err.Error())) 83 | return nil, err 84 | } 85 | return configMap, nil 86 | } 87 | 88 | func ConfigMapUpdate(cluster, namespace, name string, configData *corev1.ConfigMap) (*corev1.ConfigMap, error) { 89 | configMap, err := ConfigMapInspect(cluster, namespace, name) 90 | if err != nil { 91 | beego.Error(fmt.Sprintf("Update ConfigMap error: %v", err.Error())) 92 | return nil, common.NewInternalServerError().SetCause(err) 93 | } 94 | configMap.Data = configData.Data 95 | if ok, err := configMapValidator(configMap); !ok { 96 | return nil, err 97 | } 98 | go gitops.CommitK8sResource(cluster, []interface{}{configMap}) 99 | return configMap, nil 100 | } 101 | 102 | func ConfigMapDelete(cluster, namespace, name string) error { 103 | configMap, err := ConfigMapInspect(cluster, namespace, name) 104 | if err != nil { 105 | beego.Error(fmt.Sprintf("Delete ConfigMap error: %v", err.Error())) 106 | return common.NewInternalServerError().SetCause(err) 107 | } 108 | configMap.ObjectMeta.Annotations = labels.AddLabel(configMap.ObjectMeta.Annotations, keyword.DELETE_LABLE, keyword.DELETE_LABLE_VALUE) 109 | go gitops.CommitK8sResource(cluster, []interface{}{configMap}) 110 | check := func(param interface{}) error { 111 | _, err = ConfigMapInspect(cluster, namespace, name) 112 | if errors.IsNotFound(err) { 113 | return nil 114 | } else { 115 | return fmt.Errorf("waiting to delete......") 116 | } 117 | } 118 | result := make(chan error) 119 | go func() { 120 | result <- WaitSync(nil, SYNC_CHECK_STEP*10, SYNC_TIMEOUT*10, check) 121 | }() 122 | err = <-result 123 | return err 124 | } 125 | 126 | func configMapValidator(configMap *corev1.ConfigMap) (bool, error) { 127 | if validate.IsChineseChar(configMap.Name) { 128 | return false, common.NewBadRequest(). 129 | SetCode("NotSupportChineseChar"). 130 | SetMessage("not support chinese char") 131 | } 132 | for key, _ := range configMap.Data { 133 | if validate.IsChineseChar(key) { 134 | return false, common.NewBadRequest(). 135 | SetCode("NotSupportChineseChar"). 136 | SetMessage("not support chinese char") 137 | } 138 | } 139 | return true, nil 140 | } 141 | -------------------------------------------------------------------------------- /backend/resource/deployment.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "fmt" 5 | "kubecloud/common/keyword" 6 | "kubecloud/gitops" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/astaxie/beego" 11 | "k8s.io/api/apps/v1beta1" 12 | apiv1 "k8s.io/api/core/v1" 13 | "k8s.io/apimachinery/pkg/api/errors" 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | "k8s.io/client-go/kubernetes" 16 | "kubecloud/backend/util/labels" 17 | ) 18 | 19 | type KubeAppInterface interface { 20 | CreateOrUpdate(obj interface{}) (interface{}, error) 21 | Status(appname, podVersion string) (*AppStatus, error) 22 | Delete(obj interface{}) (interface{}, error) 23 | AppIsExisted(appname, podVersion string) (bool, error) 24 | Scale(obj interface{}, replicas int) error 25 | Restart(obj interface{}) error 26 | GetOwnerForPod(pod apiv1.Pod, ref *metav1.OwnerReference) interface{} 27 | } 28 | 29 | type AppStatus struct { 30 | ReadyReplicas int32 31 | UpdatedReplicas int32 32 | AvailableReplicas int32 33 | AvailableStatus string 34 | Message string 35 | } 36 | 37 | type DeploymentRes struct { 38 | Cluster string 39 | Namespace string 40 | client kubernetes.Interface 41 | } 42 | 43 | func NewDeploymentRes(client kubernetes.Interface, cluster, namespace string) KubeAppInterface { 44 | return &DeploymentRes{ 45 | Cluster: cluster, 46 | Namespace: namespace, 47 | client: client, 48 | } 49 | } 50 | 51 | func (kr *DeploymentRes) CreateOrUpdate(obj interface{}) (interface{}, error) { 52 | dp, ok := obj.(*v1beta1.Deployment) 53 | if !ok { 54 | return nil, fmt.Errorf("can not generate deployment object!") 55 | } 56 | beego.Info("creating or updating deployment, " + dp.Name) 57 | //go gitops.CommitK8sResource(kr.Cluster, []interface{}{dp}) 58 | return dp, nil 59 | } 60 | 61 | func (kr *DeploymentRes) Status(appname, suffix string) (*AppStatus, error) { 62 | deployment, err := kr.client.AppsV1beta1().Deployments(kr.Namespace).Get(GenerateDeployName(appname, suffix), metav1.GetOptions{}) 63 | if err != nil { 64 | return nil, err 65 | } 66 | status := &AppStatus{ 67 | ReadyReplicas: deployment.Status.ReadyReplicas, 68 | AvailableReplicas: deployment.Status.AvailableReplicas, 69 | UpdatedReplicas: deployment.Status.UpdatedReplicas, 70 | } 71 | for _, condition := range deployment.Status.Conditions { 72 | if condition.Type == v1beta1.DeploymentAvailable { 73 | status.AvailableStatus = string(condition.Status) 74 | status.Message = condition.Message 75 | break 76 | } 77 | } 78 | return status, nil 79 | } 80 | 81 | func (kr *DeploymentRes) Delete(obj interface{}) (interface{}, error) { 82 | dp, ok := obj.(*v1beta1.Deployment) 83 | if !ok { 84 | return nil, fmt.Errorf("can not generate deployment object!") 85 | } 86 | dp.ObjectMeta.Annotations = labels.AddLabel(dp.ObjectMeta.Annotations, keyword.DELETE_LABLE, keyword.DELETE_LABLE_VALUE) 87 | //go gitops.CommitK8sResource(kr.Cluster, []interface{}{dp}) 88 | beego.Warn(fmt.Sprintf("delete deployment %s successfully!", dp.Name)) 89 | return dp, nil 90 | } 91 | 92 | func (kr *DeploymentRes) AppIsExisted(appname, suffix string) (bool, error) { 93 | name := GenerateDeployName(appname, suffix) 94 | _, err := kr.client.AppsV1beta1().Deployments(kr.Namespace).Get(name, metav1.GetOptions{}) 95 | if err != nil { 96 | if !errors.IsNotFound(err) { 97 | return false, err 98 | } else { 99 | return false, nil 100 | } 101 | } 102 | return true, nil 103 | } 104 | 105 | func (kr *DeploymentRes) Scale(obj interface{}, replicas int) error { 106 | dp, ok := obj.(*v1beta1.Deployment) 107 | if !ok { 108 | return fmt.Errorf("can not generate deployment object!") 109 | } 110 | num := int32(replicas) 111 | if *dp.Spec.Replicas == num { 112 | return nil 113 | } 114 | dp.Spec.Replicas = &num 115 | go gitops.CommitK8sResource(kr.Cluster, []interface{}{dp}) 116 | return nil 117 | } 118 | 119 | func (kr *DeploymentRes) Restart(obj interface{}) error { 120 | dp, ok := obj.(*v1beta1.Deployment) 121 | if !ok { 122 | return fmt.Errorf("can not generate deployment object!") 123 | } 124 | dp.Spec.Template.ObjectMeta.Annotations = labels.AddLabel(dp.Spec.Template.ObjectMeta.Annotations, keyword.RESTART_LABLE, strconv.FormatInt(time.Now().Unix(), 10)) 125 | go gitops.CommitK8sResource(kr.Cluster, []interface{}{dp}) 126 | return nil 127 | } 128 | 129 | func (kr *DeploymentRes) GetOwnerForPod(pod apiv1.Pod, ref *metav1.OwnerReference) interface{} { 130 | if ref == nil { 131 | return nil 132 | } 133 | rs, err := kr.client.ExtensionsV1beta1().ReplicaSets(pod.Namespace).Get(ref.Name, metav1.GetOptions{}) 134 | if err != nil || rs.UID != ref.UID { 135 | beego.Warn(fmt.Sprintf("Cannot get replicaset %s for pod %s: %v", ref.Name, pod.Name, err)) 136 | return nil 137 | } 138 | // Now find the Deployment that owns that ReplicaSet. 139 | depRef := metav1.GetControllerOf(rs) 140 | if depRef == nil { 141 | return nil 142 | } 143 | // We can't look up by UID, so look up by Name and then verify UID. 144 | // Don't even try to look up by Name if it's the wrong Kind. 145 | if depRef.Kind != v1beta1.SchemeGroupVersion.WithKind("Deployment").Kind { 146 | return nil 147 | } 148 | d, err := kr.client.AppsV1beta1().Deployments(pod.Namespace).Get(depRef.Name, metav1.GetOptions{}) 149 | if err != nil { 150 | return nil 151 | } 152 | if d.UID != depRef.UID { 153 | return nil 154 | } 155 | return d 156 | } 157 | -------------------------------------------------------------------------------- /backend/resource/event.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "kubecloud/backend/dao" 5 | "time" 6 | ) 7 | 8 | type eventInfo struct { 9 | ClusterName string `json:"cluster_name"` 10 | Namespace string `json:"namespace"` 11 | EventLevel string `json:"event_level"` 12 | ObjectKind string `json:"object_kind"` 13 | ObjectName string `json:"object_name"` 14 | ObjectPhase string `json:"object_phase"` 15 | ObjectMessage string `json:"object_message"` 16 | SourceHost string `json:"source_host"` 17 | LastTime time.Time `json:"last_time"` 18 | } 19 | 20 | func GetEvents(clusterName, namespace, sourceHost, objectKind, objectName, eventLevel string, limitCount int64) ([]eventInfo, error) { 21 | eventsInfo := []eventInfo{} 22 | events, err := dao.GetEvents(clusterName, namespace, sourceHost, objectKind, objectName, eventLevel, limitCount) 23 | if err != nil { 24 | return eventsInfo, err 25 | } 26 | for _, event := range events { 27 | eventsInfo = append(eventsInfo, eventInfo{ 28 | ClusterName: event.Cluster, 29 | Namespace: event.Namespace, 30 | EventLevel: event.EventType, 31 | ObjectKind: event.ObjectKind, 32 | ObjectName: event.ObjectName, 33 | ObjectPhase: event.Reason, 34 | ObjectMessage: event.Message, 35 | SourceHost: event.SourceHost, 36 | LastTime: event.LastTimestamp, 37 | }) 38 | } 39 | return eventsInfo, nil 40 | } 41 | -------------------------------------------------------------------------------- /backend/resource/exec_command.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | type ExecRequest struct { 4 | Command string `json:"command"` 5 | } 6 | -------------------------------------------------------------------------------- /backend/resource/objectvalidator.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/astaxie/beego" 7 | v1beta1 "k8s.io/api/apps/v1beta1" 8 | apiv1 "k8s.io/api/core/v1" 9 | errors "k8s.io/apimachinery/pkg/api/errors" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/client-go/kubernetes" 12 | "kubecloud/backend/dao" 13 | "kubecloud/common" 14 | ) 15 | 16 | type ObjectValidator interface { 17 | Validator(obj interface{}) error 18 | } 19 | 20 | type KubeAppValidator struct { 21 | namespace string 22 | client kubernetes.Interface 23 | } 24 | 25 | func NewKubeAppValidator(client kubernetes.Interface, namespace string) *KubeAppValidator { 26 | return &KubeAppValidator{ 27 | namespace: namespace, 28 | client: client, 29 | } 30 | } 31 | 32 | func (validator *KubeAppValidator) Validator(obj interface{}) error { 33 | var volumes []apiv1.Volume 34 | if obj == nil { 35 | return nil 36 | } 37 | if dp, ok := obj.(*v1beta1.Deployment); ok { 38 | volumes = dp.Spec.Template.Spec.Volumes 39 | } 40 | for _, vol := range volumes { 41 | if vol.PersistentVolumeClaim != nil { 42 | _, err := validator.client.CoreV1().PersistentVolumeClaims(validator.namespace).Get(vol.PersistentVolumeClaim.ClaimName, metav1.GetOptions{}) 43 | if errors.IsNotFound(err) { 44 | return fmt.Errorf("PVC(%s) is not existed in namespace %s!", vol.PersistentVolumeClaim.ClaimName, validator.namespace) 45 | } 46 | if err != nil { 47 | beego.Error("get PVC info failed:", err) 48 | return fmt.Errorf("get PVC(%s) info failed!", vol.PersistentVolumeClaim.ClaimName) 49 | } 50 | } 51 | if vol.ConfigMap != nil { 52 | _, err := validator.client.CoreV1().ConfigMaps(validator.namespace).Get(vol.ConfigMap.Name, metav1.GetOptions{}) 53 | if errors.IsNotFound(err) { 54 | return fmt.Errorf("Configmap:%s is not existed in namespace %s!", vol.ConfigMap.Name, validator.namespace) 55 | } 56 | if err != nil { 57 | beego.Error("get configmap info failed:", err) 58 | return fmt.Errorf("get configmap:%s info failed!", vol.ConfigMap.Name) 59 | } 60 | } 61 | } 62 | return nil 63 | } 64 | 65 | type KubeSvcValidator struct { 66 | cluster string 67 | namespace string 68 | appname string 69 | } 70 | 71 | func NewKubeSvcValidator(cluster, namespace, appname string) *KubeSvcValidator { 72 | return &KubeSvcValidator{ 73 | cluster: cluster, 74 | namespace: namespace, 75 | appname: appname, 76 | } 77 | } 78 | 79 | func (validator *KubeSvcValidator) Validator(obj interface{}) error { 80 | if obj == nil { 81 | return nil 82 | } 83 | svc, ok := obj.(*apiv1.Service) 84 | if !ok { 85 | return nil 86 | } 87 | for _, port := range svc.Spec.Ports { 88 | if err := checkNodePort(validator.cluster, validator.appname, int(port.NodePort)); err != nil { 89 | return err 90 | } 91 | } 92 | 93 | return nil 94 | } 95 | 96 | func checkNodePort(cluster, newAppname string, nodePort int) error { 97 | if nodePort != 0 && cluster != "" { 98 | svcPorts, err := dao.NewK8sServiceModel().List(cluster, common.AllNamespace, nodePort) 99 | if err != nil { 100 | return err 101 | } 102 | for _, item := range svcPorts { 103 | if item.Service.OwnerName != newAppname { 104 | return fmt.Errorf("NodePort(%d) is used by application(%s/%s/%s)!", 105 | nodePort, cluster, item.Namespace, item.Service.OwnerName) 106 | } 107 | } 108 | } 109 | return nil 110 | } 111 | -------------------------------------------------------------------------------- /backend/resource/service.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "kubecloud/backend/dao" 5 | "kubecloud/common/utils" 6 | 7 | "fmt" 8 | 9 | "github.com/astaxie/beego" 10 | "github.com/astaxie/beego/orm" 11 | apiv1 "k8s.io/api/core/v1" 12 | ) 13 | 14 | type SimpleService struct { 15 | Name string `json:"name"` 16 | Namespace string `json:"namespace"` 17 | Ports []int `json:"ports"` 18 | Type apiv1.ServiceType `json:"type"` 19 | ClusterIP string `json:"clusterIP"` 20 | } 21 | 22 | type serviceAddress struct { 23 | Port int `json:"port"` 24 | TargetPort int `json:"target_port"` 25 | NodePort int `json:"node_port"` 26 | Protocol string `json:"protocol"` 27 | NodePortAddr string `json:"node_port_addr"` 28 | ClusterAddr string `json:"cluster_addr"` 29 | } 30 | 31 | type podSvcAddress struct { 32 | TargetPort int `json:"target_port"` 33 | Protocol string `json:"protocol"` 34 | ClusterAddr string `json:"cluster_addr"` 35 | } 36 | 37 | type ServiceDetail struct { 38 | Name string `json:"name"` 39 | Type string `json:"type"` 40 | ClusterIP string `json:"cluster_ip"` 41 | AddressList []serviceAddress `json:"address_list"` 42 | PodsvcAddrList []podSvcAddress `json:"podsvc_addr_list"` 43 | } 44 | 45 | type ServiceRes struct { 46 | cluster string 47 | modelSvc *dao.K8sServiceModel 48 | listNSFunc NamespaceListFunction 49 | } 50 | 51 | func NewServiceRes(cluster string, get NamespaceListFunction) *ServiceRes { 52 | return &ServiceRes{cluster: cluster, modelSvc: dao.NewK8sServiceModel(), listNSFunc: get} 53 | } 54 | 55 | func (sr *ServiceRes) GetServiceList(namespace string) ([]SimpleService, error) { 56 | list := []SimpleService{} 57 | svcPortList, err := sr.modelSvc.List(sr.cluster, namespace, 0) 58 | if err != nil { 59 | beego.Debug("error:", err) 60 | return list, err 61 | } 62 | nslist := sr.listNSFunc() 63 | for _, port := range svcPortList { 64 | // filter headless service 65 | if !utils.ContainsString(nslist, port.Namespace) || 66 | port.Service.ClusterIP == "None" { 67 | continue 68 | } 69 | index := -1 70 | for i, item := range list { 71 | if port.Service.Name == item.Name { 72 | index = i 73 | break 74 | } 75 | } 76 | if index >= 0 { 77 | // just append port 78 | list[index].Ports = append(list[index].Ports, port.Port) 79 | } else { 80 | svc := SimpleService{ 81 | Name: port.Service.Name, 82 | Namespace: port.Service.Namespace, 83 | Type: apiv1.ServiceType(port.Service.Type), 84 | ClusterIP: port.Service.ClusterIP, 85 | } 86 | svc.Ports = append(svc.Ports, port.Port) 87 | list = append(list, svc) 88 | } 89 | } 90 | 91 | return list, nil 92 | } 93 | 94 | func (sr *ServiceRes) GetService(namespace, owner, name string) ([]SimpleService, error) { 95 | service, err := sr.modelSvc.Get(sr.cluster, namespace, owner, name) 96 | if err != nil { 97 | return nil, err 98 | } 99 | ss := SimpleService{ 100 | Name: service.Name, 101 | Namespace: service.Namespace, 102 | Type: apiv1.ServiceType(service.Type), 103 | ClusterIP: service.ClusterIP, 104 | } 105 | for _, port := range service.Ports { 106 | ss.Ports = append(ss.Ports, port.Port) 107 | } 108 | 109 | return []SimpleService{ss}, nil 110 | } 111 | 112 | func (sr *ServiceRes) GetServiceDetail(namespace, name, nodeip string) (ServiceDetail, error) { 113 | svcDetail := ServiceDetail{} 114 | svc, err := sr.modelSvc.Get(sr.cluster, namespace, name, "") 115 | if err != nil { 116 | if err != orm.ErrNoRows { 117 | beego.Error("Get service information failed: " + err.Error()) 118 | return svcDetail, err 119 | } 120 | return svcDetail, nil 121 | } 122 | svcDetail.Name = svc.OwnerName 123 | svcDetail.Type = svc.Type 124 | svcDetail.ClusterIP = svc.ClusterIP 125 | for _, item := range svc.Ports { 126 | var address serviceAddress 127 | address.Port = item.Port 128 | address.TargetPort = item.TargetPort 129 | address.Protocol = item.Protocol 130 | address.NodePort = item.NodePort 131 | address.ClusterAddr = fmt.Sprintf("%s.%s:%v", svc.Name, svc.Namespace, address.Port) 132 | if apiv1.ServiceType(svcDetail.Type) == apiv1.ServiceTypeNodePort && nodeip != "" { 133 | address.NodePortAddr = fmt.Sprintf("%s:%v", nodeip, address.NodePort) 134 | } else { 135 | address.NodePortAddr = "" 136 | } 137 | svcDetail.AddressList = append(svcDetail.AddressList, address) 138 | } 139 | 140 | return svcDetail, nil 141 | } 142 | 143 | func (sr *ServiceRes) GetHeadlessSvcDetail(namespace, name string) (ServiceDetail, error) { 144 | svcDetail := ServiceDetail{} 145 | svc, err := sr.modelSvc.Get(sr.cluster, namespace, name, "") 146 | if err != nil { 147 | beego.Error("Get service information failed:", err) 148 | return svcDetail, nil 149 | } 150 | svcDetail.Name = svc.OwnerName 151 | svcDetail.Type = svc.Type 152 | svcDetail.ClusterIP = svc.ClusterIP 153 | for _, item := range svc.Ports { 154 | var address serviceAddress 155 | address.Port = item.Port 156 | address.TargetPort = item.TargetPort 157 | address.Protocol = item.Protocol 158 | address.ClusterAddr = fmt.Sprintf("%s.%s.svc.cluster.local:%v", svc.Name, svc.Namespace, address.Port) 159 | address.NodePortAddr = "" 160 | svcDetail.AddressList = append(svcDetail.AddressList, address) 161 | ep, err := dao.NewK8sEndpointModel().Get(sr.cluster, namespace, svc.Name, int32(item.TargetPort)) 162 | if err != nil { 163 | beego.Warn("get endpoint failed:", err) 164 | } else { 165 | for _, addr := range ep.Addresses { 166 | svcDetail.PodsvcAddrList = append(svcDetail.PodsvcAddrList, podSvcAddress{ 167 | TargetPort: item.TargetPort, 168 | Protocol: item.Protocol, 169 | ClusterAddr: fmt.Sprintf("%s.%s.%s:%v", addr.TargetRefName, svc.Name, svc.Namespace, item.TargetPort), 170 | }) 171 | } 172 | } 173 | } 174 | 175 | return svcDetail, nil 176 | } 177 | -------------------------------------------------------------------------------- /backend/resource/template.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/astaxie/beego/orm" 8 | 9 | "kubecloud/backend/dao" 10 | "kubecloud/backend/models" 11 | "kubecloud/common" 12 | "kubecloud/common/utils" 13 | ) 14 | 15 | type TemplateInfo struct { 16 | models.ZcloudTemplate 17 | CreateAt string `json:"create_at"` 18 | UpdateAt string `json:"update_at"` 19 | DeleteAt string `json:"delete_at"` 20 | } 21 | 22 | type TemplateRes struct { 23 | modelHandle *dao.TemplateModel 24 | listNSFunc NamespaceListFunction 25 | } 26 | 27 | func NewTemplateRes(get NamespaceListFunction) *TemplateRes { 28 | return &TemplateRes{ 29 | modelHandle: dao.NewTemplateModel(), 30 | listNSFunc: get, 31 | } 32 | } 33 | 34 | // template interface, nativetemplate support this interface 35 | type Template interface { 36 | Default(cluster string) Template 37 | Validate() error 38 | GetExample() []byte 39 | Deploy(projectid int64, cluster, namespace, tname string, eparam *ExtensionParam) error 40 | } 41 | 42 | func NewTemplate() Template { 43 | return NewNativeTemplate() 44 | } 45 | 46 | func (tr *TemplateRes) CreateTemplate(template models.ZcloudTemplate) (*models.ZcloudTemplate, error) { 47 | texist, err := tr.modelHandle.GetTemplate(template.Namespace, template.Name) 48 | if texist != nil { 49 | return nil, common.NewConflict().SetCode("TemplateAlreadyExists").SetMessage("template already exists") 50 | } else { 51 | if err != nil { 52 | if err != orm.ErrNoRows { 53 | return nil, common.NewInternalServerError().SetCause(err) 54 | } 55 | } 56 | } 57 | temp, err := tr.modelHandle.CreateTemplate(template) 58 | if err != nil { 59 | return nil, common.NewInternalServerError().SetCause(err) 60 | } 61 | 62 | return temp, nil 63 | } 64 | 65 | func (tr *TemplateRes) DeleteTemplate(namespace, name string) error { 66 | _, err := tr.modelHandle.GetTemplate(namespace, name) 67 | if err != nil { 68 | if err == orm.ErrNoRows { 69 | return common.NewNotFound().SetCause(err) 70 | } else { 71 | return common.NewInternalServerError().SetCause(err) 72 | } 73 | } 74 | 75 | //todo if template is used by some apps, it can not be deleted 76 | err = tr.modelHandle.DeleteTemplate(namespace, name) 77 | if err != nil { 78 | return common.NewInternalServerError().SetCause(err) 79 | } 80 | 81 | return nil 82 | } 83 | 84 | func (tr *TemplateRes) UpdateTemplate(template models.ZcloudTemplate) (*models.ZcloudTemplate, error) { 85 | told, err := tr.modelHandle.GetTemplate(template.Namespace, template.Name) 86 | if err != nil { 87 | if err == orm.ErrNoRows { 88 | return nil, common.NewNotFound().SetCause(err) 89 | } else { 90 | return nil, common.NewInternalServerError().SetCause(err) 91 | } 92 | } 93 | told.Kind = template.Kind 94 | told.Spec = template.Spec 95 | told.Description = template.Description 96 | 97 | err = tr.modelHandle.UpdateTemplate(*told) 98 | if err != nil { 99 | return nil, common.NewInternalServerError().SetCause(err) 100 | } 101 | 102 | return told, nil 103 | } 104 | 105 | func (tr *TemplateRes) GetTemplateList(namespace string, filterQuery *utils.FilterQuery) (*utils.QueryResult, error) { 106 | nslist := []string{} 107 | if namespace != common.AllNamespace { 108 | nslist = append(nslist, namespace) 109 | } else { 110 | nslist = tr.listNSFunc() 111 | } 112 | res, err := tr.modelHandle.GetTemplateList(nslist, filterQuery) 113 | if err != nil { 114 | if err == orm.ErrNoRows { 115 | return nil, common.NewNotFound().SetCause(err) 116 | } 117 | return nil, common.NewInternalServerError().SetCause(err) 118 | } 119 | tList, ok := res.List.([]models.ZcloudTemplate) 120 | if !ok { 121 | return nil, common.NewInternalServerError().SetCause(fmt.Errorf("invalid data type")) 122 | } 123 | list := []TemplateInfo{} 124 | for _, item := range tList { 125 | temp := TemplateInfo{} 126 | temp.ZcloudTemplate = item 127 | temp.UpdateAt = item.UpdateAt.Format("2006-01-02 15:04:05") 128 | temp.CreateAt = item.CreateAt.Format("2006-01-02 15:04:05") 129 | list = append(list, temp) 130 | } 131 | res.List = list 132 | return res, nil 133 | } 134 | 135 | func (tr *TemplateRes) GetTemplateByName(namespace, name string) (*models.ZcloudTemplate, int, error) { 136 | template, err := tr.modelHandle.GetTemplate(namespace, name) 137 | if err != nil { 138 | if err == orm.ErrNoRows { 139 | return template, http.StatusNotFound, err 140 | } 141 | return template, http.StatusInternalServerError, err 142 | } 143 | 144 | return template, http.StatusOK, nil 145 | } 146 | 147 | func (tr *TemplateRes) GetTemplateByID(id int64) (*models.ZcloudTemplate, int, error) { 148 | template, err := tr.modelHandle.GetTemplateByID(id) 149 | if err != nil { 150 | if err == orm.ErrNoRows { 151 | return template, http.StatusNotFound, err 152 | } 153 | return template, http.StatusInternalServerError, err 154 | } 155 | 156 | return template, http.StatusOK, nil 157 | } 158 | -------------------------------------------------------------------------------- /backend/service/appconfig.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "github.com/astaxie/beego/config" 4 | 5 | func GetAppConfig() config.Configer { 6 | return appConfigProvider() 7 | } 8 | -------------------------------------------------------------------------------- /backend/service/clienthelper.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | 7 | "github.com/astaxie/beego" 8 | "k8s.io/client-go/rest" 9 | "k8s.io/client-go/tools/clientcmd" 10 | ) 11 | 12 | func GetKubeRestConfig(cluster string) (*rest.Config, error) { 13 | return clientcmd.BuildConfigFromFlags("", path.Join(beego.AppConfig.String("k8s::configPath"), cluster)) 14 | } 15 | 16 | // Heapster client 17 | type HeapsterClient interface { 18 | Get(path string) RequestInterface 19 | } 20 | 21 | type InClusterHeapsterClient struct { 22 | client rest.Interface 23 | } 24 | 25 | type RequestInterface interface { 26 | DoRaw() ([]byte, error) 27 | } 28 | 29 | func NewHeapsterClient(cluster string) (HeapsterClient, error) { 30 | client, err := GetClientset(cluster) 31 | if err != nil { 32 | return nil, fmt.Errorf("get client error %v", err) 33 | } 34 | 35 | return InClusterHeapsterClient{client: client.CoreV1().RESTClient()}, nil 36 | } 37 | 38 | func (c InClusterHeapsterClient) Get(path string) RequestInterface { 39 | return c.client.Get().Prefix("proxy"). 40 | Namespace("kube-system"). 41 | Resource("services"). 42 | Name("heapster"). 43 | Suffix("/api/v1" + path) 44 | } 45 | -------------------------------------------------------------------------------- /backend/service/clientset.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "sync" 5 | 6 | "k8s.io/client-go/kubernetes" 7 | ) 8 | 9 | var ( 10 | clusterClientsetMapMutex sync.RWMutex 11 | clusterClientsetMap = make(map[string]kubernetes.Interface) 12 | ) 13 | 14 | func findClientset(cluster string) (client kubernetes.Interface, ok bool) { 15 | clusterClientsetMapMutex.RLock() 16 | defer clusterClientsetMapMutex.RUnlock() 17 | client, ok = clusterClientsetMap[cluster] 18 | return client, ok 19 | } 20 | 21 | func newClientset(cluster string) (client kubernetes.Interface, err error) { 22 | var ok bool 23 | clusterClientsetMapMutex.Lock() 24 | defer clusterClientsetMapMutex.Unlock() 25 | client, ok = clusterClientsetMap[cluster] 26 | if !ok { 27 | client, err = clientsetProvider(cluster) 28 | if err == nil { 29 | clusterClientsetMap[cluster] = client 30 | } 31 | } 32 | return client, err 33 | } 34 | 35 | func GetClientset(cluster string) (client kubernetes.Interface, err error) { 36 | var ok bool 37 | client, ok = findClientset(cluster) 38 | if !ok { 39 | client, err = newClientset(cluster) 40 | } 41 | return client, err 42 | } 43 | 44 | func UpdateClientset(cluster string) (client kubernetes.Interface, err error) { 45 | clusterClientsetMapMutex.Lock() 46 | defer clusterClientsetMapMutex.Unlock() 47 | client, err = clientsetProvider(cluster) 48 | if err != nil { 49 | return 50 | } 51 | clusterClientsetMap[cluster] = client 52 | return 53 | } 54 | -------------------------------------------------------------------------------- /backend/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | "sync" 7 | 8 | "github.com/astaxie/beego" 9 | "github.com/astaxie/beego/config" 10 | "github.com/astaxie/beego/utils" 11 | "k8s.io/client-go/kubernetes" 12 | "k8s.io/client-go/kubernetes/fake" 13 | "k8s.io/client-go/tools/clientcmd" 14 | ) 15 | 16 | var clientsetProvider func(cluster string) (kubernetes.Interface, error) 17 | 18 | var appConfigProvider func() config.Configer 19 | 20 | func Init() { 21 | clientsetProvider = func(cluster string) (kubernetes.Interface, error) { 22 | configPath := path.Join(beego.AppConfig.String("k8s::configPath"), cluster) 23 | config, err := clientcmd.BuildConfigFromFlags("", configPath) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return kubernetes.NewForConfig(config) 28 | } 29 | 30 | appConfigProvider = func() config.Configer { 31 | return beego.AppConfig 32 | } 33 | } 34 | 35 | func InitMock() { 36 | clientsetProvider = func(cluster string) (kubernetes.Interface, error) { 37 | return fake.NewSimpleClientset(), nil 38 | } 39 | 40 | appConfigContext := struct { 41 | once sync.Once 42 | configer config.Configer 43 | }{} 44 | 45 | appConfigProvider = func() config.Configer { 46 | appConfigContext.once.Do(func() { 47 | confDir := "../../conf" 48 | configPath := "" 49 | searchPath := []string{"app.local.conf", "app.test.conf"} 50 | for _, fileName := range searchPath { 51 | path := path.Join(confDir, fileName) 52 | if utils.FileExists(path) { 53 | configPath = path 54 | break 55 | } 56 | } 57 | if configPath == "" { 58 | panic(fmt.Sprintf(`config file not found, search path: %v`, searchPath)) 59 | } 60 | configer, err := config.NewConfig("ini", configPath) 61 | if err != nil { 62 | panic(err) 63 | } 64 | appConfigContext.configer = configer 65 | }) 66 | return appConfigContext.configer 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /backend/util/kubeutil/ingressutil.go: -------------------------------------------------------------------------------- 1 | package kubeutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | "kubecloud/backend/dao" 7 | "kubecloud/common/utils" 8 | 9 | extensions "k8s.io/api/extensions/v1beta1" 10 | "kubecloud/backend/util/labels" 11 | ) 12 | 13 | const ( 14 | DefaultIngressAnnotationKey = "created_default" 15 | ) 16 | 17 | func DeleteHostFromTLS(TLS []extensions.IngressTLS, delHost string) []extensions.IngressTLS { 18 | // delete host from tls 19 | // if tls.hosts is empty, delete tls from Spec.TLS 20 | newTLS := []extensions.IngressTLS{} 21 | for _, tls := range TLS { 22 | exHosts := []string{} 23 | for _, host := range tls.Hosts { 24 | if host != delHost { 25 | exHosts = append(exHosts, host) 26 | } 27 | } 28 | if len(exHosts) != 0 { 29 | tls.Hosts = exHosts 30 | newTLS = append(newTLS, tls) 31 | } 32 | } 33 | return newTLS 34 | } 35 | 36 | func MergeIngressRuleValue(first, second *extensions.IngressRule) extensions.IngressRule { 37 | if first.HTTP == nil { 38 | return *second 39 | } 40 | if second.HTTP == nil { 41 | return *first 42 | } 43 | rule := first.DeepCopy() 44 | for _, spath := range second.HTTP.Paths { 45 | found := false 46 | for _, fpath := range first.HTTP.Paths { 47 | if utils.PathsIsEqual(fpath.Path, spath.Path) { 48 | found = true 49 | break 50 | } 51 | } 52 | if !found { 53 | rule.HTTP.Paths = append(rule.HTTP.Paths, spath) 54 | } 55 | } 56 | return *rule 57 | } 58 | 59 | func MergeIngressRuleList(firstList, secondList []extensions.IngressRule) []extensions.IngressRule { 60 | for _, orule := range secondList { 61 | index := -1 62 | for i, rule := range firstList { 63 | if orule.Host == rule.Host { 64 | index = i 65 | break 66 | } 67 | } 68 | if index < 0 { 69 | firstList = append(firstList, orule) 70 | } else { 71 | firstList[index] = MergeIngressRuleValue(&firstList[index], &orule) 72 | } 73 | } 74 | return firstList 75 | } 76 | 77 | func MergeIngressTLSList(firstList, secondList []extensions.IngressTLS) []extensions.IngressTLS { 78 | // check TLS, if not existed in new ing, then add it 79 | for _, otls := range secondList { 80 | index := -1 81 | for i, ntls := range firstList { 82 | if ntls.SecretName == otls.SecretName { 83 | index = i 84 | break 85 | } 86 | } 87 | if index < 0 { 88 | firstList = deleteHostsFromTLS(firstList, otls) 89 | firstList = append(firstList, otls) 90 | } else { 91 | firstList[index] = MergeIngressTLSHost(&firstList[index], &otls) 92 | } 93 | } 94 | return firstList 95 | } 96 | 97 | func MergeIngressTLSHost(first, second *extensions.IngressTLS) extensions.IngressTLS { 98 | tls := first.DeepCopy() 99 | for _, shost := range second.Hosts { 100 | found := true 101 | for _, nhost := range first.Hosts { 102 | if shost == nhost { 103 | break 104 | } 105 | } 106 | if !found { 107 | tls.Hosts = append(tls.Hosts, shost) 108 | } 109 | } 110 | return *tls 111 | } 112 | 113 | func CheckIngressRule(cluster, namespace string, rules []extensions.IngressRule) error { 114 | ruleModel := dao.NewIngressRuleModel() 115 | for _, rule := range rules { 116 | if err := ruleModel.CheckHostUnique(cluster, namespace, rule.Host); err != nil { 117 | return err 118 | } 119 | if rule.HTTP == nil { 120 | continue 121 | } 122 | paths := []string{} 123 | for i, path := range rule.HTTP.Paths { 124 | paths = append(paths, path.Path) 125 | for j, item := range rule.HTTP.Paths { 126 | if i != j && utils.PathsIsEqual(path.Path, item.Path) { 127 | return fmt.Errorf("the path in the ingress rule is duplicated!") 128 | } 129 | } 130 | } 131 | } 132 | return nil 133 | } 134 | 135 | func SetCreatedDefaultAnno(ing *extensions.Ingress) *extensions.Ingress { 136 | if ing == nil { 137 | return nil 138 | } 139 | // set default annotation 140 | labels.AddLabel(ing.ObjectMeta.Annotations, DefaultIngressAnnotationKey, "true") 141 | return ing 142 | } 143 | 144 | func DeleteCreatedDefaultAnno(ing *extensions.Ingress) *extensions.Ingress { 145 | if ing == nil { 146 | return nil 147 | } 148 | if ing.ObjectMeta.Annotations == nil { 149 | return ing 150 | } 151 | // set default annotation 152 | delete(ing.ObjectMeta.Annotations, DefaultIngressAnnotationKey) 153 | return ing 154 | } 155 | 156 | func IngressIsCreatedDefault(ing *extensions.Ingress) bool { 157 | if ing == nil { 158 | return false 159 | } 160 | if ing.ObjectMeta.Annotations == nil { 161 | return false 162 | } 163 | return (ing.ObjectMeta.Annotations[DefaultIngressAnnotationKey] == "true") 164 | } 165 | 166 | func deleteHostsFromTLS(TLSList []extensions.IngressTLS, one extensions.IngressTLS) []extensions.IngressTLS { 167 | for _, host := range one.Hosts { 168 | TLSList = DeleteHostFromTLS(TLSList, host) 169 | } 170 | return TLSList 171 | } 172 | -------------------------------------------------------------------------------- /backend/util/kubeutil/serviceutil.go: -------------------------------------------------------------------------------- 1 | package kubeutil 2 | 3 | import ( 4 | apiv1 "k8s.io/api/core/v1" 5 | "strconv" 6 | ) 7 | 8 | func SetTrafficWeight(svc *apiv1.Service, versionKey string, weight int) { 9 | if svc == nil { 10 | return 11 | } 12 | if svc.Annotations == nil { 13 | svc.Annotations = make(map[string]string) 14 | } 15 | svc.Annotations[versionKey] = strconv.Itoa(weight) 16 | } 17 | -------------------------------------------------------------------------------- /backend/util/labels/labels.go: -------------------------------------------------------------------------------- 1 | package labels 2 | 3 | // AddLabel returns a map with the given key and value added to the given map. 4 | func AddLabel(labels map[string]string, labelKey, labelValue string) map[string]string { 5 | if labelKey == "" { 6 | // Don't need to add a label. 7 | return labels 8 | } 9 | if labels == nil { 10 | labels = make(map[string]string) 11 | } 12 | labels[labelKey] = labelValue 13 | return labels 14 | } 15 | -------------------------------------------------------------------------------- /cmd/kubecloud/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | //"flag" 5 | "runtime" 6 | 7 | "github.com/astaxie/beego" 8 | "github.com/astaxie/beego/logs" 9 | _ "github.com/astaxie/beego/session/mysql" 10 | 11 | //kubelogs "k8s.io/apiserver/pkg/util/logs" 12 | "kubecloud/backend/controllermanager" 13 | _ "kubecloud/backend/controllermanager/register" 14 | "kubecloud/backend/models" 15 | "kubecloud/backend/resource" 16 | "kubecloud/backend/service" 17 | "kubecloud/gitops" 18 | "kubecloud/routers" 19 | ) 20 | 21 | func init() { 22 | service.Init() 23 | 24 | // for glog 25 | //kubelogs.InitLogs() 26 | //defer kubelogs.FlushLogs() 27 | //flag.Parse() 28 | 29 | logFilename := beego.AppConfig.String("log::logfile") 30 | logLevel := beego.AppConfig.String("log::level") 31 | logSeparate := beego.AppConfig.String("log::separate") 32 | if logFilename == "" { 33 | logFilename = "log/kubecloud.log" 34 | } 35 | if logLevel == "" { 36 | logLevel = "7" 37 | } 38 | if logSeparate == "" { 39 | logSeparate = "[\"error\"]" 40 | } 41 | logconfig := `{ 42 | "filename": "` + logFilename + `", 43 | "level": ` + logLevel + `, 44 | "separate": ` + logSeparate + ` 45 | }` 46 | logs.SetLogger(logs.AdapterMultiFile, logconfig) 47 | 48 | // init mysql models 49 | models.Init() 50 | // init k8sConfig 51 | resource.InitK8sConfig() 52 | gitops.CloneClusterConfigRepo() 53 | 54 | controllermanager.Init() 55 | 56 | routers.Init() 57 | } 58 | 59 | func main() { 60 | beego.Info("Beego version:", beego.VERSION) 61 | beego.Info("Golang version:", runtime.Version()) 62 | beego.Run() 63 | } 64 | -------------------------------------------------------------------------------- /common/const.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | // const variables 4 | const ( 5 | AllNamespace = "all" 6 | AllCluster = "all" 7 | ReplicasMin = 0 8 | ReplicasMax = 100 9 | ) 10 | -------------------------------------------------------------------------------- /common/errors.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | k8serrors "k8s.io/apimachinery/pkg/api/errors" 8 | ) 9 | 10 | type Error struct { 11 | status int 12 | code string 13 | message string 14 | cause error 15 | } 16 | 17 | func (this *Error) Error() string { 18 | return fmt.Sprintf("Error: %v, %v, %v, %v", this.status, this.code, this.message, this.cause) 19 | } 20 | 21 | func (this *Error) Status() int { 22 | return this.status 23 | } 24 | 25 | func (this *Error) Code() string { 26 | return this.code 27 | } 28 | 29 | func (this *Error) Message() string { 30 | return this.message 31 | } 32 | 33 | func (this *Error) Cause() error { 34 | return this.cause 35 | } 36 | 37 | func (this *Error) SetCode(code string) *Error { 38 | this.code = code 39 | return this 40 | } 41 | 42 | func (this *Error) SetMessage(format string, args ...interface{}) *Error { 43 | this.message = fmt.Sprintf(format, args...) 44 | return this 45 | } 46 | 47 | func (this *Error) SetCause(err error) *Error { 48 | this.cause = err 49 | return this 50 | } 51 | 52 | // FromK8sError convert a k8s error to Error 53 | func FromK8sError(err error) *Error { 54 | if k8serrors.IsNotFound(err) { 55 | return NewNotFound().SetCause(err) 56 | } 57 | if k8serrors.IsAlreadyExists(err) || k8serrors.IsConflict(err) { 58 | return NewConflict().SetCause(err) 59 | 60 | } 61 | return NewInternalServerError().SetCause(err) 62 | } 63 | 64 | // Check following URL before add any new functions: 65 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 66 | 67 | // NewBadRequest create a bad request error 68 | func NewBadRequest() *Error { 69 | return &Error{ 70 | status: http.StatusBadRequest, 71 | code: "BadRequest", 72 | message: "bad request", 73 | } 74 | } 75 | 76 | // NewConflict create a conflict error 77 | func NewConflict() *Error { 78 | return &Error{ 79 | status: http.StatusConflict, 80 | code: "Conflict", 81 | message: "conflict", 82 | } 83 | } 84 | 85 | // NewUnauthorized create a unauthorized error 86 | func NewUnauthorized() *Error { 87 | return &Error{ 88 | status: http.StatusUnauthorized, 89 | code: "Unauthorized", 90 | message: "unauthorized", 91 | } 92 | } 93 | 94 | // NewForbidden create a forbidden error 95 | func NewForbidden() *Error { 96 | return &Error{ 97 | status: http.StatusForbidden, 98 | code: "Forbidden", 99 | message: "forbidden", 100 | } 101 | } 102 | 103 | // NewNotFound create a not found error 104 | func NewNotFound() *Error { 105 | return &Error{ 106 | status: http.StatusNotFound, 107 | code: "NotFound", 108 | message: "not found", 109 | } 110 | } 111 | 112 | // NewMethodNotAllowed create a method not allowed error 113 | func NewMethodNotAllowed() *Error { 114 | return &Error{ 115 | status: http.StatusMethodNotAllowed, 116 | code: "MethodNotAllowed", 117 | message: "method not allowed", 118 | } 119 | } 120 | 121 | // NewInternalServerError create a internal server error 122 | func NewInternalServerError() *Error { 123 | return &Error{ 124 | status: http.StatusInternalServerError, 125 | code: "InternalServerError", 126 | message: "internal server error", 127 | } 128 | } 129 | 130 | // NewNotImplemented create a not implemented error 131 | func NewNotImplemented() *Error { 132 | return &Error{ 133 | status: http.StatusNotImplemented, 134 | code: "NotImplemented", 135 | message: "not implemented", 136 | } 137 | } 138 | 139 | // NewPayloadTooLarge create a payload too large error 140 | func NewPayloadTooLarge() *Error { 141 | return &Error{ 142 | status: http.StatusRequestEntityTooLarge, 143 | code: "PayloadTooLarge", 144 | message: "payload too large", 145 | } 146 | } 147 | 148 | // NewServiceUnavailable create a service unavailabe error 149 | func NewServiceUnavailable() *Error { 150 | return &Error{ 151 | status: http.StatusServiceUnavailable, 152 | code: "ServiceUnavailable", 153 | message: "service unavailable", 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /common/errors_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | v1 "k8s.io/api/storage/v1" 9 | k8serrors "k8s.io/apimachinery/pkg/api/errors" 10 | ) 11 | 12 | func TestError(t *testing.T) { 13 | t.Run("Error", func(t *testing.T) { 14 | code := "oops" 15 | message := "don't panic" 16 | cause := fmt.Errorf("undefined reference") 17 | 18 | err := Error{} 19 | assert.Equal(t, 0, err.Status()) 20 | 21 | assert.Equal(t, &err, err.SetCode(code)) 22 | assert.Equal(t, code, err.Code()) 23 | 24 | assert.Equal(t, &err, err.SetMessage(message)) 25 | assert.Equal(t, message, err.Message()) 26 | 27 | assert.Equal(t, &err, err.SetCause(cause)) 28 | assert.Equal(t, cause, err.Cause()) 29 | 30 | assert.NotEmpty(t, err.Error()) 31 | }) 32 | 33 | t.Run("4XX", func(t *testing.T) { 34 | errs := []*Error{ 35 | NewBadRequest(), 36 | NewNotFound(), 37 | NewConflict(), 38 | NewUnauthorized(), 39 | NewForbidden(), 40 | NewMethodNotAllowed(), 41 | NewPayloadTooLarge(), 42 | } 43 | for _, err := range errs { 44 | assert.NotNil(t, err) 45 | assert.True(t, err.Status() >= 400 && err.Status() < 500, fmt.Sprintf(`should be 4XX: %#v`, err)) 46 | } 47 | }) 48 | 49 | t.Run("5XX", func(t *testing.T) { 50 | errs := []*Error{ 51 | NewNotImplemented(), 52 | NewServiceUnavailable(), 53 | NewInternalServerError(), 54 | } 55 | for _, err := range errs { 56 | assert.NotNil(t, err) 57 | assert.True(t, err.Status() >= 500 && err.Status() < 600, fmt.Sprintf(`should be 5XX: %#v`, err)) 58 | } 59 | }) 60 | 61 | t.Run("FromRawError", func(t *testing.T) { 62 | rawErr := fmt.Errorf("oops") 63 | err := FromK8sError(rawErr) 64 | assert.NotNil(t, err) 65 | assert.Equal(t, rawErr, err.Cause()) 66 | assert.Equal(t, NewInternalServerError().Status(), err.Status()) 67 | }) 68 | 69 | t.Run("FromK8sError", func(t *testing.T) { 70 | t.Run("NotFound", func(t *testing.T) { 71 | k8sErr := k8serrors.NewNotFound(v1.Resource("sc"), "not found") 72 | err := FromK8sError(k8sErr) 73 | assert.NotNil(t, err) 74 | assert.Equal(t, k8sErr, err.Cause()) 75 | assert.Equal(t, NewNotFound().Status(), err.Status()) 76 | }) 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /common/keyword/keyword.go: -------------------------------------------------------------------------------- 1 | package keyword 2 | 3 | const ( 4 | LABEL_PRIMEDEPARTENT_ID_KEY = "prime_department_id" 5 | LABEL_SUBDEPARTENT_ID_KEY = "sub_department_id" 6 | LABEL_APPNAME_KEY = "app" 7 | LABEL_APPVERSION_KEY = "appversion" 8 | LABEL_PODVERSION_KEY = "version" 9 | 10 | ISTIO_INJECTION_POLICY = "istio-injection" 11 | ISTIO_INJECTION_ENABLE = "enabled" 12 | ISTIO_INJECTION_DISABLE = "disabled" 13 | 14 | RESTART_LABLE = "kubecloud/restart" 15 | RESTART_LABLE_VALUE = "true" 16 | DELETE_LABLE = "kubecloud/delete" 17 | DELETE_LABLE_VALUE = "true" 18 | ENV_TEST_PUBLIC = "test_public" 19 | ENV_DEV = "dev" 20 | 21 | K8S_RESOURCE_TYPE_NODE = "node" 22 | K8S_RESOURCE_TYPE_APP = "app" 23 | ) 24 | -------------------------------------------------------------------------------- /common/utils/nettools.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | var ( 11 | HttpClient = &http.Client{ 12 | Transport: &http.Transport{ 13 | Dial: (&net.Dialer{ 14 | Timeout: 10 * time.Second, 15 | }).Dial, 16 | MaxIdleConns: 200, 17 | MaxIdleConnsPerHost: 200, 18 | IdleConnTimeout: 30 * time.Second, 19 | TLSHandshakeTimeout: 5 * time.Second, 20 | TLSClientConfig: &tls.Config{ 21 | InsecureSkipVerify: true, 22 | }, 23 | }, 24 | Timeout: 60 * time.Second, 25 | } 26 | ) 27 | 28 | func TcpConnTest(server string) error { 29 | conn, err := net.DialTimeout("tcp", server, time.Second*3) 30 | if err != nil { 31 | return err 32 | } 33 | defer conn.Close() 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /common/utils/ormfilter.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego/orm" 6 | "kubecloud/common/validate" 7 | "reflect" 8 | "strings" 9 | ) 10 | 11 | type FilterContext struct { 12 | FilterKey string `json:"filter_key"` 13 | FilterVal interface{} `json:"filter_val"` 14 | Operator string `json:"operator"` 15 | } 16 | 17 | type FilterQuery struct { 18 | PageIndex int `json:"page_index"` 19 | PageSize int `json:"page_size"` 20 | FilterContext `json:",inline"` 21 | IsLike bool `json:"is_like"` 22 | RequestBody interface{} `json:"request_body"` 23 | } 24 | 25 | type DefaultFilter struct { 26 | Filters []FilterContext 27 | } 28 | 29 | const ( 30 | FilterOperatorIn = "in" 31 | FilterOperatorEqual = "" 32 | FilterOperatorExclude = "not" 33 | 34 | DEF_PAGE_INDEX = 1 35 | DEF_PAGE_SIZE = 10 36 | ) 37 | 38 | func NewFilterQuery(isLike bool) *FilterQuery { 39 | return &FilterQuery{IsLike: isLike} 40 | } 41 | 42 | func (filter *FilterQuery) FilterCondition(filterKeys []string) *orm.Condition { 43 | filterSuffix := "icontains" 44 | cond := orm.NewCondition() 45 | if filter == nil { 46 | return nil 47 | } 48 | var items []string 49 | v := reflect.ValueOf(filter.FilterVal) 50 | switch v.Kind() { 51 | case reflect.String: 52 | val := filter.FilterVal.(string) 53 | if val == "" { 54 | return nil 55 | } 56 | items = strings.Split(val, ";") 57 | if validate.IsChineseChar(val) { 58 | filterSuffix = "contains" 59 | } 60 | case reflect.Int, reflect.Int64: 61 | if v.Int() == 0 { 62 | return nil 63 | } 64 | default: 65 | return nil 66 | } 67 | for i, item := range items { 68 | items[i] = strings.TrimSpace(item) 69 | } 70 | if filter.IsLike { 71 | for _, key := range filterKeys { 72 | if len(items) > 1 { 73 | for _, item := range items { 74 | cond = cond.Or(fmt.Sprintf("%s__%s", key, filterSuffix), item) 75 | } 76 | } else { 77 | cond = cond.Or(fmt.Sprintf("%s__%s", key, filterSuffix), filter.FilterVal) 78 | } 79 | } 80 | } else { 81 | if filter.FilterKey != "" { 82 | if len(items) > 1 { 83 | cond = cond.And(filter.FilterKey+"__in", items) 84 | } else { 85 | cond = cond.And(filter.FilterKey, filter.FilterVal) 86 | } 87 | } else { 88 | for _, key := range filterKeys { 89 | if len(items) > 1 { 90 | for _, item := range items { 91 | cond = cond.Or(key, item) 92 | } 93 | } else { 94 | cond = cond.Or(key, filter.FilterVal) 95 | } 96 | } 97 | } 98 | } 99 | if cond.IsEmpty() { 100 | return nil 101 | } 102 | return cond 103 | } 104 | 105 | func (filter *FilterQuery) SetFilter(key string, val interface{}, operator string) *FilterQuery { 106 | filter.FilterVal = val 107 | filter.FilterKey = key 108 | filter.Operator = operator 109 | return filter 110 | } 111 | 112 | func NewDefaultFilter() *DefaultFilter { 113 | return &DefaultFilter{} 114 | } 115 | 116 | func (filter *DefaultFilter) DefaultFilterCondition() *orm.Condition { 117 | if filter == nil { 118 | return nil 119 | } 120 | cond := orm.NewCondition() 121 | for _, f := range filter.Filters { 122 | v := reflect.ValueOf(f.FilterVal) 123 | switch f.Operator { 124 | case FilterOperatorIn: 125 | if (v.Kind() == reflect.Array || v.Kind() == reflect.Slice) && v.Len() != 0 { 126 | cond = cond.And(f.FilterKey+"__in", f.FilterVal) 127 | } 128 | case FilterOperatorEqual: 129 | switch v.Kind() { 130 | case reflect.String: 131 | if f.FilterVal.(string) != "" { 132 | cond = cond.And(f.FilterKey, f.FilterVal) 133 | } 134 | default: 135 | cond = cond.And(f.FilterKey, f.FilterVal) 136 | } 137 | case FilterOperatorExclude: 138 | switch v.Kind() { 139 | case reflect.String: 140 | if f.FilterVal.(string) != "" { 141 | cond = cond.AndNot(f.FilterKey, f.FilterVal) 142 | } 143 | default: 144 | cond = cond.AndNot(f.FilterKey, f.FilterVal) 145 | } 146 | } 147 | } 148 | if cond.IsEmpty() { 149 | return nil 150 | } 151 | return cond 152 | } 153 | 154 | func (filter *DefaultFilter) AppendFilter(key string, val interface{}, op string) *DefaultFilter { 155 | filter.Filters = append(filter.Filters, FilterContext{FilterKey: key, FilterVal: val, Operator: op}) 156 | return filter 157 | } 158 | -------------------------------------------------------------------------------- /common/utils/simplelocker.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | 8 | "k8s.io/api/core/v1" 9 | "k8s.io/apimachinery/pkg/api/errors" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/client-go/kubernetes" 12 | corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 13 | ) 14 | 15 | const ( 16 | LockerRecordAnnotationKey = "zcloud.kubernetes.io/locker" 17 | LockerStatusUnkown = "unkown" 18 | LockerStatusAcquire = "acquire" 19 | LockerStatusRelease = "release" 20 | LockerAutoReleaseDuration = int64(60 * time.Second) //90s 21 | ) 22 | 23 | type LockerRecord struct { 24 | TimeStamp int64 //nano level 25 | Identity string 26 | Status string 27 | } 28 | 29 | type EndpointLocker struct { 30 | Namespace string 31 | Name string 32 | Client corev1.CoreV1Interface 33 | e *v1.Endpoints 34 | } 35 | 36 | func NewSimpleLocker(namespace, name string, client kubernetes.Interface) *EndpointLocker { 37 | return &EndpointLocker{ 38 | Namespace: namespace, 39 | Name: name, 40 | Client: client.CoreV1(), 41 | } 42 | } 43 | 44 | // Get returns the election record from a Endpoints Annotation 45 | func (cl *EndpointLocker) Get() (*LockerRecord, error) { 46 | var record LockerRecord 47 | var err error 48 | cl.e, err = cl.Client.Endpoints(cl.Namespace).Get(cl.Name, metav1.GetOptions{}) 49 | if err != nil { 50 | return nil, err 51 | } 52 | if cl.e.Annotations == nil { 53 | cl.e.Annotations = make(map[string]string) 54 | } 55 | if recordBytes, found := cl.e.Annotations[LockerRecordAnnotationKey]; found { 56 | if err := json.Unmarshal([]byte(recordBytes), &record); err != nil { 57 | return nil, err 58 | } 59 | } 60 | return &record, nil 61 | } 62 | 63 | // Create attempts to create a LeaderElectionRecord annotation 64 | func (cl *EndpointLocker) Create(ler LockerRecord) error { 65 | var err error 66 | cl.e, err = cl.Client.Endpoints(cl.Namespace).Get(cl.Name, metav1.GetOptions{}) 67 | if err == nil { 68 | return nil 69 | } 70 | if !errors.IsNotFound(err) { 71 | return err 72 | } 73 | recordBytes, err := json.Marshal(ler) 74 | if err != nil { 75 | return err 76 | } 77 | cl.e, err = cl.Client.Endpoints(cl.Namespace).Create(&v1.Endpoints{ 78 | ObjectMeta: metav1.ObjectMeta{ 79 | Name: cl.Name, 80 | Namespace: cl.Namespace, 81 | Annotations: map[string]string{ 82 | LockerRecordAnnotationKey: string(recordBytes), 83 | }, 84 | }, 85 | }) 86 | 87 | return err 88 | } 89 | 90 | // Update will update and existing annotation on a given resource. 91 | func (cl *EndpointLocker) Update(ler LockerRecord) (string, error) { 92 | var err error 93 | if cl.e == nil { 94 | return LockerStatusUnkown, fmt.Errorf("endpoints not initialized, call get or create first") 95 | } 96 | cl.e, err = cl.Client.Endpoints(cl.Namespace).Get(cl.Name, metav1.GetOptions{}) 97 | recordBytes, err := json.Marshal(ler) 98 | if err != nil { 99 | return LockerStatusUnkown, err 100 | } 101 | info := cl.e.Annotations[LockerRecordAnnotationKey] 102 | if info != "" && ler.Status == LockerStatusAcquire { 103 | curLockerRecord := LockerRecord{ 104 | Status: LockerStatusRelease, 105 | } 106 | json.Unmarshal([]byte(info), &curLockerRecord) 107 | now := time.Now().UnixNano() 108 | timeDiff := int64(0) 109 | if now >= curLockerRecord.TimeStamp { 110 | timeDiff = now - curLockerRecord.TimeStamp 111 | } 112 | if curLockerRecord.Status == LockerStatusAcquire && timeDiff < LockerAutoReleaseDuration { 113 | return curLockerRecord.Status, fmt.Errorf("the locker is locked, please wait!") 114 | } 115 | } 116 | cl.e.Annotations[LockerRecordAnnotationKey] = string(recordBytes) 117 | cl.e, err = cl.Client.Endpoints(cl.Namespace).Update(cl.e) 118 | return ler.Status, err 119 | } 120 | -------------------------------------------------------------------------------- /common/utils/synclocker.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type locker struct { 8 | lock *sync.Mutex 9 | count int 10 | } 11 | 12 | type SyncLocker struct { 13 | lockerList map[string]*locker 14 | mapLocker *sync.Mutex 15 | } 16 | 17 | func NewSyncLocker() *SyncLocker { 18 | lock := &SyncLocker{ 19 | mapLocker: &sync.Mutex{}, 20 | } 21 | lock.lockerList = make(map[string]*locker) 22 | 23 | return lock 24 | } 25 | 26 | func (l *SyncLocker) Lock(key string) { 27 | var tmp *locker 28 | l.mapLocker.Lock() 29 | if l.lockerList[key] == nil { 30 | tmp = &locker{ 31 | lock: &sync.Mutex{}, 32 | count: 0, 33 | } 34 | l.lockerList[key] = tmp 35 | } else { 36 | tmp = l.lockerList[key] 37 | } 38 | l.lockerList[key].count++ 39 | l.mapLocker.Unlock() 40 | tmp.lock.Lock() 41 | } 42 | 43 | func (l *SyncLocker) Unlock(key string) { 44 | if l.lockerList[key] == nil { 45 | return 46 | } 47 | var tmp *locker 48 | l.mapLocker.Lock() 49 | tmp = l.lockerList[key] 50 | l.lockerList[key].count-- 51 | if l.lockerList[key].count == 0 { 52 | delete(l.lockerList, key) 53 | } 54 | l.mapLocker.Unlock() 55 | tmp.lock.Unlock() 56 | } 57 | -------------------------------------------------------------------------------- /common/utils/visgraph.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type ( 10 | font struct { 11 | Size int `json:"size"` 12 | } 13 | highlight struct { 14 | Border string `json:"border"` 15 | Background string `json:"background"` 16 | } 17 | NodeColor struct { 18 | Border string `json:"border"` 19 | Background string `json:"background"` 20 | Highlight highlight `json:"highlight"` 21 | } 22 | EdgeColor struct { 23 | Color string `json:"color"` 24 | Highlight string `json:"highlight"` 25 | } 26 | VisNode struct { 27 | Id string `json:"id"` 28 | Label string `json:"label"` 29 | Shape string `json:"shape"` 30 | Color NodeColor `json:"color"` 31 | Size int `json:"size"` 32 | Font font `json:"font"` 33 | BorderWidth int `json:"borderWidth"` 34 | } 35 | VisEdge struct { 36 | Id string `json:"id"` 37 | Label string `json:"label"` 38 | From string `json:"from"` 39 | To string `json:"to"` 40 | Arrows string `json:"arrows"` 41 | Color EdgeColor `json:"color"` 42 | LabelMap map[string]string `json:"-"` 43 | Length int `json:"length"` 44 | Width int `json:"width"` 45 | } 46 | 47 | VisGraph struct { 48 | Nodes []VisNode `json:"nodes"` 49 | Edges []VisEdge `json:"edges"` 50 | } 51 | ) 52 | 53 | const ( 54 | UNKOWN_NODE = "unknown" 55 | USER = "user" 56 | DEFAULT_COLOR = "#848690" 57 | HIGHLIGHT_COLOR = "#621ba4" 58 | NODE_BACKGROUND_COLOR = "#fff" 59 | NODE_BORDER_COLOR = "#848690" 60 | NODE_SIZE = 40 61 | NODE_FONT_SIZE = 28 62 | NODE_BORDER_WIDTH = 2 63 | NODE_SHAPE = "ellipse" 64 | EDGE_LENGTH = 220 65 | EDGE_WIDTH = 2 66 | ) 67 | 68 | func NewVisGraph() *VisGraph { 69 | return &VisGraph{} 70 | } 71 | 72 | // AddEdge adds a new edge to an existing dynamic graph. 73 | func (g *VisGraph) AddEdge(src, target string, lbls map[string]string) { 74 | var sn, tn *VisNode 75 | sn = g.getNode(src) 76 | if sn == nil { 77 | sn = g.newNode(src) 78 | g.Nodes = append(g.Nodes, *sn) 79 | } 80 | tn = g.getNode(target) 81 | if tn == nil { 82 | tn = g.newNode(target) 83 | g.Nodes = append(g.Nodes, *tn) 84 | } 85 | e := VisEdge{ 86 | Id: sn.Id + "→" + tn.Id, 87 | From: sn.Id, 88 | To: tn.Id, 89 | Arrows: "to", 90 | Color: EdgeColor{ 91 | Color: DEFAULT_COLOR, 92 | Highlight: HIGHLIGHT_COLOR, 93 | }, 94 | LabelMap: make(map[string]string), 95 | Length: EDGE_LENGTH, 96 | Width: EDGE_WIDTH, 97 | } 98 | index := g.checkEdgeExist(e) 99 | if index == -1 { 100 | e.LabelMap = lbls 101 | g.Edges = append(g.Edges, e) 102 | } else { 103 | for nk, nv := range lbls { 104 | value, exist := g.Edges[index].LabelMap[nk] 105 | if !exist { 106 | g.Edges[index].LabelMap[nk] = nv 107 | } else { 108 | if nv != value { 109 | g.Edges[index].LabelMap[nk] = nv 110 | } 111 | } 112 | } 113 | } 114 | } 115 | 116 | func (g *VisGraph) MakeEdgeLabel() { 117 | for i, e := range g.Edges { 118 | g.Edges[i].Label = labelStr(e.LabelMap) 119 | } 120 | } 121 | 122 | func (g *VisGraph) checkEdgeExist(e VisEdge) int { 123 | for i, item := range g.Edges { 124 | if item.From == e.From && 125 | item.To == e.To && 126 | item.Arrows == e.Arrows { 127 | return i 128 | } 129 | } 130 | 131 | return -1 132 | } 133 | 134 | func (g *VisGraph) getNode(name string) *VisNode { 135 | for _, node := range g.Nodes { 136 | if node.Label == name { 137 | return &node 138 | } 139 | } 140 | 141 | return nil 142 | } 143 | 144 | func (g *VisGraph) newNode(name string) *VisNode { 145 | //maxId := 0 146 | //label := name 147 | //for _, node := range g.Nodes { 148 | // if node.Id > maxId { 149 | // maxId = node.Id 150 | // } 151 | //} 152 | //if name == UNKOWN_NODE { 153 | // label = USER 154 | //} 155 | return &VisNode{ 156 | Id: name, 157 | Label: name, 158 | Color: NodeColor{ 159 | Border: NODE_BORDER_COLOR, 160 | Background: NODE_BACKGROUND_COLOR, 161 | Highlight: highlight{ 162 | Border: HIGHLIGHT_COLOR, 163 | Background: NODE_BACKGROUND_COLOR, 164 | }, 165 | }, 166 | Shape: NODE_SHAPE, 167 | Size: NODE_SIZE, 168 | BorderWidth: NODE_BORDER_WIDTH, 169 | Font: font{ 170 | Size: NODE_FONT_SIZE, 171 | }, 172 | } 173 | } 174 | 175 | func labelStr(m map[string]string) string { 176 | var labelBuf bytes.Buffer 177 | for _, v := range m { 178 | labelBuf.WriteString(fmt.Sprintf("%s\n", v)) 179 | } 180 | return strings.TrimRight(labelBuf.String(), "\n") 181 | } 182 | -------------------------------------------------------------------------------- /common/utils/vizgraph.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "io" 4 | 5 | type ( 6 | // Static represents a service graph generated by API calls that is 7 | // meant to persist across generation requests. It must be merged with 8 | // a Dynamic graph to provide a complete service graph for Istio. 9 | Static struct { 10 | Nodes map[string]struct{} 11 | } 12 | 13 | // Dynamic represents a service graph generated on the fly. 14 | Dynamic struct { 15 | Nodes map[string]struct{} `json:"nodes"` 16 | Edges []*Edge `json:"edges"` 17 | } 18 | 19 | // Edge represents an edge in a dynamic service graph. 20 | Edge struct { 21 | Source string `json:"source"` 22 | Target string `json:"target"` 23 | Labels Attributes `json:"labels"` 24 | } 25 | 26 | // Attributes contain a set of annotations for an edge. 27 | Attributes map[string]string 28 | 29 | // SerializeFn provides a mechanism for writing out the service graph. 30 | SerializeFn func(w io.Writer, g *Dynamic) error 31 | ) 32 | 33 | func NewVizGraph() *Dynamic { 34 | return &Dynamic{Nodes: map[string]struct{}{}, Edges: []*Edge{}} 35 | } 36 | 37 | // AddEdge adds a new edge to an existing dynamic graph. 38 | func (d *Dynamic) AddEdge(src, target string, lbls map[string]string) { 39 | d.Edges = append(d.Edges, &Edge{src, target, lbls}) 40 | d.Nodes[src] = struct{}{} 41 | d.Nodes[target] = struct{}{} 42 | } 43 | 44 | // Merge adds all of the nodes in the static graph into the dynamic graph. 45 | func (d *Dynamic) Merge(static *Static) { 46 | for node := range static.Nodes { 47 | d.Nodes[node] = struct{}{} 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | appname = kubecloud 2 | httpport = 8080 3 | runmode = dev 4 | copyrequestbody = true 5 | 6 | [k8s] 7 | configPath = ./k8sconfig 8 | domainSuffix = 9 | defaultDepartment = 10 | defaultBizCluster = 11 | timeout = 120 12 | nodeMgrYamlsPath=./yamls 13 | resourcelock_namespace = kubecloud 14 | clusterMode = one 15 | environment = dev 16 | syncResourceDisable=false 17 | syncResourcePeriod=5 18 | 19 | [DB] 20 | databaseUrl = ***:***@tcp(***:3306)/***?charset=utf8 21 | databaseDebug = false 22 | defaultRowsLimit = 5000 23 | 24 | [log] 25 | logfile = "log/kubecloud.log" 26 | perm = 0640 27 | level = 7 28 | separate = ["error"] 29 | -------------------------------------------------------------------------------- /controllers/clusters.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/astaxie/beego/orm" 7 | 8 | "kubecloud/backend/resource" 9 | "kubecloud/common" 10 | "kubecloud/common/utils" 11 | ) 12 | 13 | type ClusterController struct { 14 | BaseController 15 | } 16 | 17 | func (cc *ClusterController) ClusterList() { 18 | filter := cc.GetFilterQuery() 19 | res, err := resource.GetClusterList(filter) 20 | if err != nil { 21 | cc.ServeError(common.NewInternalServerError().SetCause(err)) 22 | return 23 | } 24 | if res.Base.PageSize == 0 { 25 | // compatible old model 26 | cc.Data["json"] = NewResult(true, res.List, "") 27 | } else { 28 | cc.Data["json"] = NewResult(true, res, "") 29 | } 30 | cc.ServeJSON() 31 | } 32 | 33 | func (cc *ClusterController) InspectCluster() { 34 | clusterId := cc.GetStringFromPath(":cluster") 35 | 36 | result, err := resource.GetClusterDetail(clusterId) 37 | if err != nil { 38 | if err == orm.ErrNoRows { 39 | cc.ServeError(common.NewNotFound().SetCause(fmt.Errorf("database error: cluster(%s) is not existed!", clusterId))) 40 | } else if err == orm.ErrMultiRows { 41 | cc.ServeError(common.NewConflict().SetCause(fmt.Errorf("database error: cluster(%s) info is duplicated: %s!", clusterId, err.Error()))) 42 | } else { 43 | cc.ServeError(common.NewInternalServerError().SetCause(err)) 44 | } 45 | return 46 | } 47 | cc.Data["json"] = NewResult(true, result, "") 48 | cc.ServeJSON() 49 | } 50 | 51 | func (cc *ClusterController) CreateCluster() { 52 | var cluster resource.Cluster 53 | cc.DecodeJSONReq(&cluster) 54 | if cluster.ClusterId == "" { 55 | cluster.ClusterId = utils.NewUUID() 56 | } 57 | if err := cluster.Verify(); err != nil { 58 | cc.ServeError(common.NewBadRequest().SetCause(err)) 59 | return 60 | } 61 | result, err := resource.CreateCluster(cluster) 62 | if err != nil { 63 | cc.ServeError(common.NewInternalServerError().SetCause(err)) 64 | return 65 | } 66 | 67 | cc.Data["json"] = NewResult(true, result, "") 68 | cc.ServeJSON() 69 | } 70 | 71 | func (cc *ClusterController) DeleteCluster() { 72 | clusterId := cc.GetStringFromPath(":cluster") 73 | 74 | if err := resource.DeleteCluster(clusterId); err != nil { 75 | cc.ServeError(common.NewInternalServerError().SetCause(err)) 76 | return 77 | } 78 | cc.Data["json"] = NewResult(true, nil, "") 79 | cc.ServeJSON() 80 | } 81 | 82 | func (cc *ClusterController) UpdateCluster() { 83 | clusterId := cc.GetStringFromPath(":cluster") 84 | 85 | var cluster resource.Cluster 86 | cc.DecodeJSONReq(&cluster) 87 | cluster.ClusterId = clusterId 88 | if err := cluster.Verify(); err != nil { 89 | cc.ServeError(common.NewBadRequest().SetCause(err)) 90 | return 91 | } 92 | 93 | result, err := resource.UpdateCluster(cluster) 94 | if err != nil { 95 | cc.ServeError(common.NewInternalServerError().SetCause(err)) 96 | return 97 | } 98 | cc.Data["json"] = NewResult(true, result, "") 99 | cc.ServeJSON() 100 | } 101 | 102 | func (cc *ClusterController) Certificate() { 103 | clusterId := cc.GetStringFromPath(":cluster") 104 | 105 | var cluster resource.Cluster 106 | cc.DecodeJSONReq(&cluster) 107 | if err := cluster.Verify(); err != nil { 108 | cc.ServeError(common.NewBadRequest().SetCause(err)) 109 | return 110 | } 111 | 112 | if err := resource.SetClusterCertificate(clusterId, cluster.Certificate); err != nil { 113 | cc.ServeError(common.NewInternalServerError().SetCause(err)) 114 | return 115 | } 116 | 117 | cc.Data["json"] = NewResult(true, nil, "") 118 | cc.ServeJSON() 119 | } 120 | -------------------------------------------------------------------------------- /controllers/common.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "kubecloud/backend/dao" 5 | "kubecloud/common" 6 | ) 7 | 8 | // TODO: deprecate IsSuccess, Origin 9 | type Result struct { 10 | IsSuccess bool `json:"IsSuccess"` 11 | Data interface{} `json:"Data,omitempty"` 12 | ErrCode string `json:"ErrCode,omitempty"` 13 | ErrMsg string `json:"ErrMsg,omitempty"` 14 | ErrDetail string `json:"ErrDetail,omitempty"` 15 | Origin string `json:"Origin,omitempty"` 16 | } 17 | 18 | func NewResult(isSuccess bool, data interface{}, errMsg string) *Result { 19 | return &Result{IsSuccess: isSuccess, Data: data, ErrMsg: errMsg} 20 | } 21 | 22 | func NewErrorResult(errCode, errMsg, errDetail string) *Result { 23 | return &Result{ 24 | IsSuccess: false, 25 | ErrCode: errCode, 26 | ErrMsg: errMsg, 27 | ErrDetail: errDetail, 28 | } 29 | } 30 | 31 | //NamespaceListFunc ... 32 | func NamespaceListFunc(cluster string, initns string) func() []string { 33 | return func() []string { 34 | nsList := []string{} 35 | if initns == common.AllNamespace { 36 | nsList, _ = dao.GetClusterNamespaceList(cluster) 37 | } else { 38 | nsList = append(nsList, initns) 39 | } 40 | return nsList 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /controllers/configmap.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | 6 | "kubecloud/backend/resource" 7 | ) 8 | 9 | type ConfigMapController struct { 10 | BaseController 11 | } 12 | 13 | func (cc *ConfigMapController) List() { 14 | clusterId := cc.GetStringFromPath(":cluster") 15 | namespace := cc.GetStringFromPath(":namespace") 16 | result, err := resource.ConfigMapList(clusterId, namespace) 17 | if err != nil { 18 | cc.ServeError(err) 19 | return 20 | } 21 | 22 | cc.Data["json"] = NewResult(true, result, "") 23 | cc.ServeJSON() 24 | } 25 | 26 | func (cc *ConfigMapController) Create() { 27 | clusterId := cc.GetStringFromPath(":cluster") 28 | namespace := cc.GetStringFromPath(":namespace") 29 | 30 | var configMapSpec corev1.ConfigMap 31 | cc.DecodeJSONReq(&configMapSpec) 32 | configMapSpec.ObjectMeta.Namespace = namespace 33 | if configMapSpec.ObjectMeta.Annotations == nil { 34 | configMapSpec.ObjectMeta.Annotations = make(map[string]string) 35 | } 36 | result, err := resource.ConfigMapCreate(clusterId, &configMapSpec) 37 | if err != nil { 38 | cc.ServeError(err) 39 | return 40 | } 41 | 42 | cc.Data["json"] = NewResult(true, result, "") 43 | cc.ServeJSON() 44 | } 45 | 46 | func (cc *ConfigMapController) Inspect() { 47 | clusterId := cc.GetStringFromPath(":cluster") 48 | namespace := cc.GetStringFromPath(":namespace") 49 | name := cc.GetStringFromPath(":configmap") 50 | 51 | result, err := resource.ConfigMapInspect(clusterId, namespace, name) 52 | if err != nil { 53 | cc.ServeError(err) 54 | return 55 | } 56 | cc.Data["json"] = NewResult(true, result, "") 57 | cc.ServeJSON() 58 | } 59 | 60 | func (cc *ConfigMapController) Update() { 61 | clusterId := cc.GetStringFromPath(":cluster") 62 | namespace := cc.GetStringFromPath(":namespace") 63 | name := cc.GetStringFromPath(":configmap") 64 | 65 | var configData corev1.ConfigMap 66 | cc.DecodeJSONReq(&configData) 67 | result, err := resource.ConfigMapUpdate(clusterId, namespace, name, &configData) 68 | if err != nil { 69 | cc.ServeError(err) 70 | return 71 | } 72 | cc.Data["json"] = NewResult(true, result, "") 73 | cc.ServeJSON() 74 | } 75 | 76 | func (cc *ConfigMapController) Delete() { 77 | clusterId := cc.GetStringFromPath(":cluster") 78 | namespace := cc.GetStringFromPath(":namespace") 79 | name := cc.GetStringFromPath(":configmap") 80 | 81 | if err := resource.ConfigMapDelete(clusterId, namespace, name); err != nil { 82 | cc.ServeError(err) 83 | return 84 | } 85 | cc.Data["json"] = NewResult(true, nil, "") 86 | cc.ServeJSON() 87 | } 88 | -------------------------------------------------------------------------------- /controllers/error.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "kubecloud/common" 5 | ) 6 | 7 | type ErrorController struct { 8 | BaseController 9 | } 10 | 11 | func (this *ErrorController) Error404() { 12 | err := common.NewNotFound() 13 | this.ServeError(err) 14 | } 15 | 16 | func (this *ErrorController) Error405() { 17 | err := common.NewMethodNotAllowed() 18 | this.ServeError(err) 19 | } 20 | 21 | func (this *ErrorController) Error501() { 22 | err := common.NewNotImplemented() 23 | this.ServeError(err) 24 | } 25 | -------------------------------------------------------------------------------- /controllers/event.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | 6 | "kubecloud/backend/resource" 7 | "kubecloud/common" 8 | ) 9 | 10 | type EventsController struct { 11 | BaseController 12 | } 13 | 14 | func (this *EventsController) Get() { 15 | clusterName := this.Ctx.Input.Query("cluster_name") 16 | namespace := this.Ctx.Input.Query("namespace") 17 | sourceHost := this.Ctx.Input.Query("source_host") 18 | objectKind := this.Ctx.Input.Query("object_kind") 19 | objectName := this.Ctx.Input.Query("object_name") 20 | eventLevel := this.Ctx.Input.Query("event_level") 21 | limitCount, err := this.GetInt64FromQuery("limit_count") 22 | if err != nil { 23 | beego.Error("Parse int error of limit_count: ", err.Error()) 24 | this.ServeError(common.NewInternalServerError().SetCause(err)) 25 | return 26 | } 27 | 28 | events, err := resource.GetEvents(clusterName, namespace, sourceHost, objectKind, objectName, eventLevel, limitCount) 29 | if err != nil { 30 | beego.Error("Get all events occur err: ", err.Error()) 31 | this.ServeError(common.NewInternalServerError().SetCause(err)) 32 | return 33 | } 34 | this.Data["json"] = NewResult(true, events, "") 35 | this.ServeJSON() 36 | } 37 | -------------------------------------------------------------------------------- /controllers/namespaces.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "kubecloud/backend/resource" 5 | "net/http" 6 | ) 7 | 8 | type NamespaceController struct { 9 | BaseController 10 | } 11 | 12 | func (this *NamespaceController) NamespaceList() { 13 | clusterId := this.GetStringFromPath(":cluster") 14 | filterQuery := this.GetFilterQuery() 15 | res, err := resource.NamespaceGetAll(clusterId, filterQuery) 16 | if err != nil { 17 | this.ServeError(err) 18 | return 19 | } 20 | this.ServeResult(NewResult(true, res, "")) 21 | } 22 | 23 | func (this *NamespaceController) Create() { 24 | clusterId := this.GetStringFromPath(":cluster") 25 | var data resource.NamespaceData 26 | this.DecodeJSONReq(&data) 27 | if err := resource.NamespaceValidate(&data); err != nil { 28 | this.ServeError(err) 29 | return 30 | } 31 | row, err := resource.NamespaceCreate(clusterId, &data) 32 | if err != nil { 33 | this.ServeError(err) 34 | return 35 | } 36 | this.SetStatus(http.StatusCreated) 37 | this.ServeResult(NewResult(true, row, "")) 38 | } 39 | 40 | func (this *NamespaceController) Update() { 41 | clusterId := this.GetStringFromPath(":cluster") 42 | namespace := this.GetStringFromPath(":namespace") 43 | var data resource.NamespaceData 44 | this.DecodeJSONReq(&data) 45 | data.Name = namespace 46 | if err := resource.NamespaceValidate(&data); err != nil { 47 | this.ServeError(err) 48 | return 49 | } 50 | row, err := resource.NamespaceUpdate(clusterId, &data) 51 | if err != nil { 52 | this.ServeError(err) 53 | return 54 | } 55 | this.ServeResult(NewResult(true, row, "")) 56 | } 57 | 58 | func (this *NamespaceController) Delete() { 59 | clusterId := this.GetStringFromPath(":cluster") 60 | namespace := this.GetStringFromPath(":namespace") 61 | if err := resource.NamespaceDelete(clusterId, namespace); err != nil { 62 | this.ServeError(err) 63 | return 64 | } 65 | this.ServeResult(NewResult(true, nil, "")) 66 | } 67 | 68 | func (this *NamespaceController) Inspect() { 69 | clusterId := this.GetStringFromPath(":cluster") 70 | namespace := this.GetStringFromPath(":namespace") 71 | row, err := resource.NamespaceGetOne(clusterId, namespace) 72 | if err != nil { 73 | this.ServeError(err) 74 | return 75 | } 76 | this.ServeResult(NewResult(true, row, "")) 77 | } 78 | -------------------------------------------------------------------------------- /controllers/node.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "kubecloud/backend/resource" 5 | "kubecloud/common" 6 | ) 7 | 8 | type NodeController struct { 9 | BaseController 10 | } 11 | 12 | func (nc *NodeController) NodeList() { 13 | clusterId := nc.GetStringFromPath(":cluster") 14 | filter := nc.GetFilterQuery() 15 | result, err := resource.GetNodeListFilter(clusterId, filter) 16 | if err != nil { 17 | nc.ServeError(common.NewInternalServerError().SetCause(err)) 18 | return 19 | } 20 | if result.Base.PageSize == 0 { 21 | // compatible old model 22 | nc.Data["json"] = NewResult(true, result.List, "") 23 | } else { 24 | nc.Data["json"] = NewResult(true, result, "") 25 | } 26 | nc.ServeJSON() 27 | } 28 | 29 | func (nc *NodeController) NodeInspect() { 30 | clusterId := nc.GetStringFromPath(":cluster") 31 | nodeName := nc.GetStringFromPath(":node") 32 | 33 | result, err := resource.GetNodeDetail(clusterId, nodeName) 34 | if err != nil { 35 | nc.ServeError(common.NewInternalServerError().SetCause(err)) 36 | return 37 | } 38 | nc.Data["json"] = NewResult(true, result, "") 39 | nc.ServeJSON() 40 | } 41 | 42 | func (nc *NodeController) NodeUpdate() { 43 | clusterId := nc.GetStringFromPath(":cluster") 44 | node := nc.GetStringFromPath(":node") 45 | 46 | var nodeUpdate resource.NodeUpdate 47 | nc.DecodeJSONReq(&nodeUpdate) 48 | nodeUpdate.Cluster = clusterId 49 | if err := nodeUpdate.Verify(); err != nil { 50 | nc.ServeError(common.NewBadRequest().SetCause(err)) 51 | return 52 | } 53 | 54 | if err := resource.UpdateNode(clusterId, node, nodeUpdate); err != nil { 55 | nc.ServeError(common.NewInternalServerError().SetCause(err)) 56 | return 57 | } 58 | nc.Data["json"] = NewResult(true, nil, "") 59 | nc.ServeJSON() 60 | } 61 | 62 | func (nc *NodeController) NodeDelete() { 63 | clusterId := nc.GetStringFromPath(":cluster") 64 | node := nc.GetStringFromPath(":node") 65 | 66 | if err := resource.DeleteNode(clusterId, node); err != nil { 67 | nc.ServeError(common.NewInternalServerError().SetCause(err)) 68 | return 69 | } 70 | 71 | nc.Data["json"] = NewResult(true, nil, "") 72 | nc.ServeJSON() 73 | } 74 | 75 | func (nc *NodeController) NodeFreeze() { 76 | clusterId := nc.GetStringFromPath(":cluster") 77 | node := nc.GetStringFromPath(":node") 78 | 79 | var nodeFreeze resource.NodeFreeze 80 | nc.DecodeJSONReq(&nodeFreeze) 81 | 82 | if err := resource.FreezeNode(clusterId, node, nodeFreeze.DeletePods); err != nil { 83 | nc.ServeError(common.NewInternalServerError().SetCause(err)) 84 | return 85 | } 86 | nc.Data["json"] = NewResult(true, nil, "") 87 | nc.ServeJSON() 88 | } 89 | 90 | func (nc *NodeController) NodeUnfreeze() { 91 | clusterId := nc.GetStringFromPath(":cluster") 92 | node := nc.GetStringFromPath(":node") 93 | 94 | if err := resource.UnfreezeNode(clusterId, node); err != nil { 95 | nc.ServeError(common.NewInternalServerError().SetCause(err)) 96 | return 97 | } 98 | nc.Data["json"] = NewResult(true, nil, "") 99 | nc.ServeJSON() 100 | } 101 | 102 | func (nc *NodeController) NodePods() { 103 | clusterId := nc.GetStringFromPath(":cluster") 104 | nodeName := nc.GetStringFromPath(":node") 105 | 106 | result, err := resource.GetNodePods(clusterId, nodeName) 107 | if err != nil { 108 | nc.ServeError(common.NewInternalServerError().SetCause(err)) 109 | return 110 | } 111 | nc.Data["json"] = NewResult(true, result, "") 112 | nc.ServeJSON() 113 | } 114 | 115 | func (nc *NodeController) NodeEvent() { 116 | filter := nc.GetFilterQuery() 117 | clusterId := nc.GetStringFromPath(":cluster") 118 | nodeName := nc.GetStringFromPath(":node") 119 | 120 | result, err := resource.GetNodeEvent(clusterId, nodeName, filter) 121 | if err != nil { 122 | nc.ServeError(common.NewInternalServerError().SetCause(err)) 123 | return 124 | } 125 | nc.Data["json"] = NewResult(true, result, "") 126 | nc.ServeJSON() 127 | } 128 | -------------------------------------------------------------------------------- /controllers/secret.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "kubecloud/backend/resource" 7 | "kubecloud/common" 8 | "kubecloud/common/utils" 9 | 10 | "github.com/astaxie/beego" 11 | ) 12 | 13 | const MAX_TLS_FILE_SIZE = 10240 14 | 15 | type SecretController struct { 16 | BaseController 17 | } 18 | 19 | type FileSizer interface { 20 | Size() int64 21 | } 22 | 23 | // List get all secret of cluster's namespace 24 | func (sc *SecretController) List() { 25 | clusterId := sc.GetStringFromPath(":cluster") 26 | namespace := sc.GetStringFromPath(":namespace") 27 | filterQuery := sc.GetFilterQuery() 28 | 29 | sHandle, err := resource.NewSecretRes(clusterId, NamespaceListFunc(clusterId, namespace)) 30 | if err != nil { 31 | beego.Error("Get secrets failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace+".") 32 | sc.ServeError(common.NewInternalServerError().SetCause(err)) 33 | return 34 | } 35 | 36 | result, err := sHandle.ListSecret(namespace, filterQuery) 37 | if err != nil { 38 | beego.Error("Get secrets failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace+".") 39 | sc.ServeError(common.NewInternalServerError().SetCause(err)) 40 | return 41 | } 42 | 43 | sc.ServeResult(NewResult(true, result, "")) 44 | } 45 | 46 | // Create create a secret 47 | func (sc *SecretController) Create() { 48 | clusterId := sc.GetStringFromPath(":cluster") 49 | namespace := sc.GetStringFromPath(":namespace") 50 | 51 | request := new(resource.SecretRequest) 52 | sc.DecodeJSONReq(&request) 53 | 54 | sHandle, err := resource.NewSecretRes(clusterId, nil) 55 | if err != nil { 56 | beego.Error("Create secret failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace, "secret name: "+request.Name+".") 57 | sc.ServeError(common.NewInternalServerError().SetCause(err)) 58 | return 59 | } 60 | err = sHandle.Validate(request) 61 | if err != nil { 62 | beego.Error("Create secret failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace, "secret name: "+request.Name+".") 63 | sc.ServeError(err) 64 | return 65 | } 66 | err = sHandle.CreateSecret(namespace, request) 67 | if err != nil { 68 | beego.Error("Create secret failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace+".") 69 | sc.ServeError(err) 70 | return 71 | } 72 | 73 | beego.Info("Create secret successfully:", "cluster: "+clusterId+",", "namespace: "+namespace, "secret name: "+request.Name+".") 74 | sc.Data["json"] = NewResult(true, nil, "") 75 | sc.ServeJSON() 76 | } 77 | 78 | // Update update a secret 79 | func (sc *SecretController) Update() { 80 | clusterId := sc.GetStringFromPath(":cluster") 81 | namespace := sc.GetStringFromPath(":namespace") 82 | name := sc.GetStringFromPath(":secret") 83 | 84 | request := new(resource.SecretRequest) 85 | sc.DecodeJSONReq(request) 86 | request.Name = name 87 | 88 | sHandle, err := resource.NewSecretRes(clusterId, nil) 89 | if err != nil { 90 | beego.Error("Update secret failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace, "secret name: "+name+".") 91 | sc.ServeError(common.NewInternalServerError().SetCause(err)) 92 | return 93 | } 94 | err = sHandle.Validate(request) 95 | if err != nil { 96 | beego.Error("Update secret failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace, "secret name: "+name+".") 97 | sc.ServeError(err) 98 | return 99 | } 100 | 101 | err = sHandle.UpdateSecret(namespace, request) 102 | if err != nil { 103 | beego.Error("Update secret failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace, "secret name: "+name+".") 104 | sc.ServeError(err) 105 | return 106 | } 107 | 108 | beego.Info("Update secret successfully:", "cluster: "+clusterId+",", "namespace: "+namespace, "secret name: "+name+".") 109 | sc.Data["json"] = NewResult(true, nil, "") 110 | sc.ServeJSON() 111 | } 112 | 113 | // Delete delete a secret 114 | func (sc *SecretController) Delete() { 115 | clusterId := sc.GetStringFromPath(":cluster") 116 | namespace := sc.GetStringFromPath(":namespace") 117 | name := sc.GetStringFromPath(":secret") 118 | secret, err := resource.NewSecretRes(clusterId, nil) 119 | if err != nil { 120 | sc.ServeError(common.NewInternalServerError().SetCause(err)) 121 | beego.Error("Delete secret failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace, "secretname: "+name+".") 122 | return 123 | } 124 | ir, err := resource.NewIngressRes(clusterId, nil, NamespaceListFunc(clusterId, namespace)) 125 | filterQuery := utils.NewFilterQuery(false).SetFilter("secret_name", name, utils.FilterOperatorEqual) 126 | result, err := ir.ListIngresses(clusterId, namespace, "", filterQuery) 127 | if err != nil { 128 | sc.ServeError(common.NewInternalServerError().SetCause(err)) 129 | beego.Error("Delete secret failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace, "secretname: "+name+".") 130 | return 131 | } 132 | if list, ok := result.List.([]resource.Ingress); ok && len(list) != 0 { 133 | beego.Error("Delete secret failed: "+"the secret has been used by some ingresses, can not be delete!", "cluster: "+clusterId+",", "namespace: "+namespace, "secretname: "+name+".") 134 | beego.Debug("ingress:", list, "hava used this secret!") 135 | sc.ServeError(common.NewConflict().SetCause(fmt.Errorf(`the secret has been used by some ingress, can not be delete`))) 136 | return 137 | } 138 | err = secret.DeleteSecret(namespace, name) 139 | if err != nil { 140 | sc.ServeError(common.NewInternalServerError().SetCause(err)) 141 | beego.Error("Delete secret failed: "+err.Error(), "cluster: "+clusterId+",", "namespace: "+namespace, "secretname: "+name+".") 142 | return 143 | } 144 | 145 | sc.Data["json"] = NewResult(true, nil, "") 146 | sc.ServeJSON() 147 | beego.Info("Delete secret successfully!", "cluster: "+clusterId+",", "namespace: "+namespace+",", "secretname: "+name+"!") 148 | } 149 | -------------------------------------------------------------------------------- /controllers/services.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/astaxie/beego" 7 | 8 | "kubecloud/backend/resource" 9 | "kubecloud/common" 10 | ) 11 | 12 | type ServiceController struct { 13 | BaseController 14 | } 15 | 16 | func (this *ServiceController) List() { 17 | clusterId := this.GetStringFromPath(":cluster") 18 | namespace := this.Ctx.Input.Param(":namespace") 19 | if clusterId == "" || namespace == "" { 20 | beego.Error("cluster: " + clusterId + " or namespace: " + namespace + " is not right, can not be empty") 21 | err := fmt.Errorf("invalid cluster or namespace") 22 | this.ServeError(common.NewBadRequest().SetCause(err)) 23 | return 24 | } 25 | srHandle := resource.NewServiceRes(clusterId, NamespaceListFunc(clusterId, namespace)) 26 | result, err := srHandle.GetServiceList(namespace) 27 | if err != nil { 28 | this.Data["json"] = NewResult(false, result, err.Error()) 29 | } else { 30 | this.Data["json"] = NewResult(true, result, "") 31 | } 32 | this.ServeJSON() 33 | } 34 | 35 | func (this *ServiceController) Inspect() { 36 | clusterId := this.GetStringFromPath(":cluster") 37 | namespace := this.Ctx.Input.Param(":namespace") 38 | name := this.Ctx.Input.Param(":service") 39 | if clusterId == "" || namespace == "" { 40 | beego.Error("cluster: " + clusterId + " or namespace: " + namespace + " is not right, can not be empty") 41 | err := fmt.Errorf("cluster or namespace can not be empty") 42 | this.ServeError(common.NewBadRequest().SetCause(err)) 43 | return 44 | } 45 | if name == "" { 46 | beego.Error("service name can not be empty") 47 | err := fmt.Errorf("service name can not be empty") 48 | this.ServeError(common.NewBadRequest().SetCause(err)) 49 | return 50 | } 51 | srHandle := resource.NewServiceRes(clusterId, nil) 52 | result, err := srHandle.GetService(namespace, name, "") 53 | if err != nil { 54 | beego.Error("get service"+"("+clusterId, namespace, name+")"+"information failed for", err) 55 | this.Data["json"] = NewResult(false, nil, err.Error()) 56 | } else { 57 | this.Data["json"] = NewResult(true, result, "") 58 | } 59 | 60 | this.ServeJSON() 61 | } 62 | -------------------------------------------------------------------------------- /controllers/version.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | type VersionController struct { 4 | BaseController 5 | } 6 | 7 | type version struct { 8 | Version string `json:"version"` 9 | } 10 | 11 | const curVersion = "V0.1.0" 12 | 13 | func (this *VersionController) Get() { 14 | ver := &version{ 15 | Version: curVersion, 16 | } 17 | 18 | this.Data["json"] = NewResult(true, ver, "") 19 | this.ServeJSON() 20 | } 21 | -------------------------------------------------------------------------------- /deploy/kubecloud.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: kubecloud 5 | --- 6 | apiVersion: v1 7 | data: 8 | app.conf: | 9 | appname = kubecloud 10 | httpport = 8080 11 | runmode = dev 12 | copyrequestbody = true 13 | 14 | [k8s] 15 | configPath = ./k8sconfig 16 | domainSuffix = 17 | defaultDepartment = 18 | defaultBizCluster = 19 | timeout = 120 20 | nodeMgrYamlsPath=./yamls 21 | resourcelock_namespace = kubecloud 22 | clusterMode = one 23 | environment = dev 24 | syncResourceDisable=false 25 | syncResourcePeriod=5 26 | 27 | [DB] 28 | databaseUrl = ***:***@tcp(***:3306)/kubecloud?charset=utf8 29 | databaseDebug = false 30 | defaultRowsLimit = 5000 31 | 32 | [log] 33 | logfile = "log/kubecloud.log" 34 | perm = 0640 35 | level = 7 36 | separate = ["error"] 37 | kind: ConfigMap 38 | metadata: 39 | name: kubecloud-config 40 | namespace: kubecloud 41 | --- 42 | apiVersion: apps/v1beta1 43 | kind: Deployment 44 | metadata: 45 | labels: 46 | app: kubecloud 47 | name: kubecloud 48 | namespace: kubecloud 49 | spec: 50 | replicas: 1 51 | selector: 52 | matchLabels: 53 | app: kubecloud 54 | strategy: 55 | rollingUpdate: 56 | maxSurge: 25% 57 | maxUnavailable: 25% 58 | type: RollingUpdate 59 | template: 60 | metadata: 61 | labels: 62 | app: kubecloud 63 | name: kubecloud 64 | namespace: kubecloud 65 | spec: 66 | containers: 67 | - image: kubecloud 68 | imagePullPolicy: Always 69 | livenessProbe: 70 | failureThreshold: 5 71 | initialDelaySeconds: 30 72 | periodSeconds: 60 73 | successThreshold: 1 74 | tcpSocket: 75 | port: 8080 76 | timeoutSeconds: 2 77 | name: kubecloud 78 | readinessProbe: 79 | failureThreshold: 3 80 | httpGet: 81 | path: /health 82 | port: 8080 83 | initialDelaySeconds: 10 84 | periodSeconds: 30 85 | successThreshold: 1 86 | timeoutSeconds: 10 87 | resources: 88 | requests: 89 | cpu: "1" 90 | memory: 1Gi 91 | securityContext: 92 | privileged: false 93 | volumeMounts: 94 | - mountPath: /kubecloud/conf 95 | name: kubecloud-config 96 | dnsPolicy: ClusterFirst 97 | restartPolicy: Always 98 | volumes: 99 | - configMap: 100 | name: kubecloud-config 101 | name: kubecloud-config 102 | status: {} 103 | --- 104 | apiVersion: v1 105 | kind: Service 106 | metadata: 107 | labels: 108 | app: kubecloud 109 | name: svc-kubecloud 110 | namespace: kubecloud 111 | spec: 112 | ports: 113 | - name: http-8080-8080 114 | port: 8080 115 | protocol: TCP 116 | targetPort: 8080 117 | selector: 118 | app: kubecloud 119 | sessionAffinity: ClientIP 120 | type: ClusterIP 121 | status: 122 | loadBalancer: {} -------------------------------------------------------------------------------- /docs/images/kubecloud-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhongAnTech/kubecloud/0fc068f5f55a7d10935dcecf52e0813b1a4fa6a3/docs/images/kubecloud-architecture.png -------------------------------------------------------------------------------- /gitops/git.go: -------------------------------------------------------------------------------- 1 | package gitops 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/golang/glog" 9 | 10 | "kubecloud/common/utils" 11 | ) 12 | 13 | type Git struct { 14 | Dir string 15 | Url string 16 | Branch string 17 | Token string 18 | commandName string 19 | } 20 | 21 | func NewGit(dir, url, branch, token string) *Git { 22 | return &Git{ 23 | Dir: dir, 24 | Url: url, 25 | Branch: branch, 26 | Token: token, 27 | commandName: "git", 28 | } 29 | } 30 | 31 | func (g *Git) configUserEmail() error { 32 | args := []string{"config", "user.email", `"kubecloud@example.com"`} 33 | out, err := utils.ExecCommand(g.Dir, g.commandName, args...) 34 | if err != nil { 35 | glog.Errorf(`git config user.email "kubecloud@example.com" error: %s`, out) 36 | return fmt.Errorf(out) 37 | } 38 | glog.Infof(`git config user.name "kubecloud"`) 39 | args = []string{"config", "user.name", `"kubecloud"`} 40 | out, err = utils.ExecCommand(g.Dir, "git", args...) 41 | if err != nil { 42 | glog.Errorf(`git config user.name "kubecloud" error: %s`, out) 43 | return fmt.Errorf(out) 44 | } 45 | glog.Infof(`git config user.name "kubecloud"`) 46 | return nil 47 | } 48 | 49 | func (g *Git) Clone() error { 50 | currentDir, _ := filepath.Abs(`.`) 51 | urlWithToken := strings.ReplaceAll(g.Url, "//", fmt.Sprintf("//kubecloud:%s@", g.Token)) 52 | args := []string{"clone", urlWithToken, "-b", g.Branch, g.Dir} 53 | out, err := utils.ExecCommand(currentDir, g.commandName, args...) 54 | if err != nil && !strings.Contains(out, "already exists and is not an empty directory") { 55 | glog.Errorf("git clone %s -b %s %s error: %s", g.Url, g.Branch, g.Dir, out) 56 | return fmt.Errorf(out) 57 | } 58 | glog.Infof("git clone %s -b %s", g.Url, g.Branch) 59 | glog.Infoln(out) 60 | if err := g.configUserEmail(); err != nil { 61 | return fmt.Errorf(out) 62 | } 63 | return nil 64 | } 65 | 66 | func (g *Git) Commit(files []string, msg string) error { 67 | for _, f := range files { 68 | args := []string{"add", f} 69 | out, err := utils.ExecCommand(g.Dir, g.commandName, args...) 70 | if err != nil { 71 | glog.Errorf("git add %s error: %s", f, out) 72 | return fmt.Errorf(out) 73 | } 74 | glog.Infof("git add %s", f) 75 | } 76 | args := []string{"commit", "-m", fmt.Sprintf(`"%s"`, msg)} 77 | out, err := utils.ExecCommand(g.Dir, g.commandName, args...) 78 | if err != nil && !strings.Contains(out, "nothing to commit, working tree clean") { 79 | glog.Errorf("git commit -m %s error: %s", fmt.Sprintf(`"%s"`, msg), out) 80 | return fmt.Errorf(out) 81 | } 82 | glog.Infof("git commit -m %s", fmt.Sprintf(`"%s"`, msg)) 83 | glog.Infoln(out) 84 | return nil 85 | } 86 | 87 | func (g *Git) Pull() error { 88 | args := []string{"pull", "origin", g.Branch} 89 | out, err := utils.ExecCommand(g.Dir, g.commandName, args...) 90 | if err != nil { 91 | glog.Errorf("git pull origin %s error: %s", g.Branch, out) 92 | return fmt.Errorf(out) 93 | } 94 | glog.Infof("git pull origin %s", g.Branch) 95 | glog.Infoln(out) 96 | return nil 97 | } 98 | 99 | func (g *Git) Push() error { 100 | args := []string{"push", "origin", g.Branch} 101 | out, err := utils.ExecCommand(g.Dir, g.commandName, args...) 102 | if err != nil { 103 | glog.Errorf("git push origin %s error: %s", g.Branch, out) 104 | return fmt.Errorf(out) 105 | } 106 | glog.Infof("git push origin %s", g.Branch) 107 | glog.Infoln(out) 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module kubecloud 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/astaxie/beego v1.12.0 7 | github.com/ghodss/yaml v1.0.0 8 | github.com/go-sql-driver/mysql v1.4.1 9 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b 10 | github.com/gorilla/websocket v1.4.1 // indirect 11 | github.com/prometheus/client_golang v1.0.0 12 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect 13 | github.com/stretchr/testify v1.4.0 14 | gopkg.in/igm/sockjs-go.v2 v2.0.1 15 | gopkg.in/yaml.v2 v2.2.4 16 | k8s.io/api v0.17.0 17 | k8s.io/apimachinery v0.17.0 18 | k8s.io/apiserver v0.17.0 19 | k8s.io/client-go v0.17.0 20 | k8s.io/component-base v0.17.0 21 | ) 22 | --------------------------------------------------------------------------------