package controller import ( "context" "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8svalidation "k8s.io/apimachinery/pkg/util/validation" "net/http" "strconv" "github.com/go-chi/chi" "github.com/goodrain/rainbond/api/handler" "github.com/goodrain/rainbond/api/model" "github.com/goodrain/rainbond/api/util/bcode" ctxutil "github.com/goodrain/rainbond/api/util/ctx" "github.com/goodrain/rainbond/db" dbmodel "github.com/goodrain/rainbond/db/model" "github.com/goodrain/rainbond/pkg/component/k8s" httputil "github.com/goodrain/rainbond/util/http" "github.com/sirupsen/logrus" ) // ApplicationController - type ApplicationController struct{} // CreateApp - func (a *ApplicationController) CreateApp(w http.ResponseWriter, r *http.Request) { var tenantReq model.Application if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &tenantReq, nil) { return } if tenantReq.AppType == model.AppTypeHelm { if tenantReq.AppStoreName == "" { httputil.ReturnBcodeError(r, w, bcode.NewBadRequest("the field 'app_tore_name' is required")) return } if tenantReq.AppTemplateName == "" { httputil.ReturnBcodeError(r, w, bcode.NewBadRequest("the field 'app_template_name' is required")) return } if tenantReq.AppName == "" { httputil.ReturnBcodeError(r, w, bcode.NewBadRequest("the field 'helm_app_name' is required")) return } if tenantReq.Version == "" { httputil.ReturnBcodeError(r, w, bcode.NewBadRequest("the field 'version' is required")) return } tenantReq.K8sApp = tenantReq.AppTemplateName } if tenantReq.K8sApp != "" { if len(k8svalidation.IsQualifiedName(tenantReq.K8sApp)) > 0 { httputil.ReturnBcodeError(r, w, bcode.ErrInvaildK8sApp) return } } // get current tenant tenant := r.Context().Value(ctxutil.ContextKey("tenant")).(*dbmodel.Tenants) tenantReq.TenantID = tenant.UUID // create app app, err := handler.GetApplicationHandler().CreateApp(r.Context(), &tenantReq) if err != nil { logrus.Errorf("create app: %+v", err) httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, app) } // BatchCreateApp - func (a *ApplicationController) BatchCreateApp(w http.ResponseWriter, r *http.Request) { var apps model.CreateAppRequest if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &apps, nil) { return } // get current tenant tenant := r.Context().Value(ctxutil.ContextKey("tenant")).(*dbmodel.Tenants) respList, err := handler.GetApplicationHandler().BatchCreateApp(r.Context(), &apps, tenant.UUID) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, respList) } // UpdateApp - func (a *ApplicationController) UpdateApp(w http.ResponseWriter, r *http.Request) { var updateAppReq model.UpdateAppRequest if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &updateAppReq, nil) { return } app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) if updateAppReq.K8sApp != "" && len(k8svalidation.IsQualifiedName(updateAppReq.K8sApp)) > 0 { httputil.ReturnBcodeError(r, w, bcode.ErrInvaildK8sApp) return } if updateAppReq.K8sApp == "" { updateAppReq.K8sApp = fmt.Sprintf("app-%s", app.AppID[:8]) if app.K8sApp != "" { updateAppReq.K8sApp = app.K8sApp } } // update app app, err := handler.GetApplicationHandler().UpdateApp(r.Context(), app, updateAppReq) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, app) } // ListApps - func (a *ApplicationController) ListApps(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() appName := query.Get("app_name") pageQuery := query.Get("page") pageSizeQuery := query.Get("pageSize") page, _ := strconv.Atoi(pageQuery) if page == 0 { page = 1 } pageSize, _ := strconv.Atoi(pageSizeQuery) if pageSize == 0 { pageSize = 10 } // get current tenantID tenantID := r.Context().Value(ctxutil.ContextKey("tenant_id")).(string) // List apps resp, err := handler.GetApplicationHandler().ListApps(tenantID, appName, page, pageSize) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, resp) } // ListComponents - func (a *ApplicationController) ListComponents(w http.ResponseWriter, r *http.Request) { appID := chi.URLParam(r, "app_id") query := r.URL.Query() pageQuery := query.Get("page") pageSizeQuery := query.Get("pageSize") page, _ := strconv.Atoi(pageQuery) if page == 0 { page = 1 } pageSize, _ := strconv.Atoi(pageSizeQuery) if pageSize == 0 { pageSize = 10 } // List services resp, err := handler.GetServiceManager().GetServicesByAppID(appID, page, pageSize) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, resp) } // DeleteApp - func (a *ApplicationController) DeleteApp(w http.ResponseWriter, r *http.Request) { app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) var req model.EtcdCleanReq if httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil) { logrus.Debugf("delete app etcd keys : %+v", req.Keys) handler.GetCleanDateBaseHandler().CleanAllServiceData(req.Keys) } // 清理与应用相关的 Kubernetes 资源 if err := cleanupAppKubernetesResources(app.TenantID, app.AppID); err != nil { logrus.Errorf("cleanup app kubernetes resources error: %v", err) } // Delete application err := handler.GetApplicationHandler().DeleteApp(r.Context(), app) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, nil) } // DeleteK8sApp - func (a *ApplicationController) DeleteK8sApp(w http.ResponseWriter, r *http.Request) { appID := chi.URLParam(r, "k8s_app") // get current tenant tenant := r.Context().Value(ctxutil.ContextKey("tenant")).(*dbmodel.Tenants) // Delete application by k8sapp err := handler.GetApplicationHandler().DeleteAppByK8sApp(tenant.UUID, appID) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, nil) } // BatchUpdateComponentPorts update component ports in batch. func (a *ApplicationController) BatchUpdateComponentPorts(w http.ResponseWriter, r *http.Request) { var appPorts []*model.AppPort if err := httputil.ReadEntity(r, &appPorts); err != nil { httputil.ReturnBcodeError(r, w, err) return } for _, port := range appPorts { if err := httputil.ValidateStruct(port); err != nil { httputil.ReturnBcodeError(r, w, err) return } } appID := r.Context().Value(ctxutil.ContextKey("app_id")).(string) if err := handler.GetApplicationHandler().BatchUpdateComponentPorts(appID, appPorts); err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, nil) } // GetAppStatus returns the status of the application. func (a *ApplicationController) GetAppStatus(w http.ResponseWriter, r *http.Request) { app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) res, err := handler.GetApplicationHandler().GetStatus(r.Context(), app) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, res) } // Install installs the application. func (a *ApplicationController) Install(w http.ResponseWriter, r *http.Request) { app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) var installAppReq model.InstallAppReq if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &installAppReq, nil) { return } if err := handler.GetApplicationHandler().Install(r.Context(), app, installAppReq.Overrides); err != nil { httputil.ReturnBcodeError(r, w, err) return } } // ListServices returns the list fo the application. func (a *ApplicationController) ListServices(w http.ResponseWriter, r *http.Request) { app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) services, err := handler.GetApplicationHandler().ListServices(r.Context(), app) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, services) } // BatchBindService - func (a *ApplicationController) BatchBindService(w http.ResponseWriter, r *http.Request) { appID := chi.URLParam(r, "app_id") var bindServiceReq model.BindServiceRequest if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &bindServiceReq, nil) { return } // bind service err := handler.GetApplicationHandler().BatchBindService(appID, bindServiceReq) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, nil) } // ListHelmAppReleases returns the list of helm releases. func (a *ApplicationController) ListHelmAppReleases(w http.ResponseWriter, r *http.Request) { app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) releases, err := handler.GetApplicationHandler().ListHelmAppReleases(r.Context(), app) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, releases) } // ListAppStatuses returns the status of the applications. func (a *ApplicationController) ListAppStatuses(w http.ResponseWriter, r *http.Request) { var req model.AppStatusesReq if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil) { return } res, err := handler.GetApplicationHandler().ListAppStatuses(r.Context(), req.AppIDs) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, res) } // ListGovernanceMode list governance mode. func (a *ApplicationController) ListGovernanceMode(w http.ResponseWriter, r *http.Request) { governance, err := handler.GetApplicationHandler().ListGovernanceMode() if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, governance) } // CheckGovernanceMode check governance mode. func (a *ApplicationController) CheckGovernanceMode(w http.ResponseWriter, r *http.Request) { governanceMode := r.URL.Query().Get("governance_mode") err := handler.GetApplicationHandler().CheckGovernanceMode(r.Context(), governanceMode) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, nil) } // CreateGovernanceModeCR create governance mode cr. func (a *ApplicationController) CreateGovernanceModeCR(w http.ResponseWriter, r *http.Request) { app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) var req model.CreateUpdateGovernanceModeReq if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil) { return } content, err := handler.GetApplicationHandler().CreateServiceMeshCR(app, req.Provisioner) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, content) } // UpdateGovernanceModeCR update governance mode cr func (a *ApplicationController) UpdateGovernanceModeCR(w http.ResponseWriter, r *http.Request) { app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) var req model.CreateUpdateGovernanceModeReq if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil) { return } content, err := handler.GetApplicationHandler().UpdateServiceMeshCR(app, req.Provisioner) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, content) } // DeleteGovernanceModeCR delete governance mode cr func (a *ApplicationController) DeleteGovernanceModeCR(w http.ResponseWriter, r *http.Request) { app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) err := handler.GetApplicationHandler().DeleteServiceMeshCR(app) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, nil) } // GetWatchOperatorManaged get watch operator managed component func (a *ApplicationController) GetWatchOperatorManaged(w http.ResponseWriter, r *http.Request) { app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) ret, err := handler.GetApplicationHandler().GetAndHandleOperatorManaged(app.AppID) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, ret) } // ChangeVolumes Since the component name supports modification, the storage directory of stateful components will change. // This interface is used to modify the original directory name to the storage directory that will actually be used. func (a *ApplicationController) ChangeVolumes(w http.ResponseWriter, r *http.Request) { app := r.Context().Value(ctxutil.ContextKey("application")).(*dbmodel.Application) err := handler.GetApplicationHandler().ChangeVolumes(app) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, nil) } // cleanupAppKubernetesResources 清理与应用相关的 K8s 资源 func cleanupAppKubernetesResources(tenantID, appID string) error { tenant, err := db.GetManager().TenantDao().GetTenantByUUID(tenantID) if err != nil { logrus.Errorf("get tenant by id error: %v", err) return err } namespace := tenant.Namespace if namespace == "" { namespace = tenantID // fallback to tenantID as namespace } // 清理与应用相关的 ApisixRoute 资源 if err := cleanupAppApisixRoutes(namespace, appID); err != nil { logrus.Errorf("cleanup app apisix routes error: %v", err) } // 清理与应用相关的 Service 资源 if err := cleanupAppServices(namespace, appID); err != nil { logrus.Errorf("cleanup app services error: %v", err) } logrus.Infof("cleanup kubernetes resources for app %s in namespace %s completed", appID, namespace) return nil } // cleanupAppApisixRoutes 清理应用相关的 ApisixRoute 资源 func cleanupAppApisixRoutes(namespace, appID string) error { ctx := context.Background() // 直接删除与该应用相关的所有 ApisixRoute err := k8s.Default().ApiSixClient.ApisixV2().ApisixRoutes(namespace).DeleteCollection( ctx, metav1.DeleteOptions{}, metav1.ListOptions{ LabelSelector: "app_id=" + appID, }, ) if err != nil { logrus.Errorf("delete apisix routes for app %s error: %v", appID, err) return err } logrus.Infof("deleted apisix routes for app %s in namespace %s", appID, namespace) return nil } // cleanupAppServices 清理应用相关的 Service 资源 func cleanupAppServices(namespace, appID string) error { ctx := context.Background() // 先列出与该应用相关的所有 Service serviceList, err := k8s.Default().Clientset.CoreV1().Services(namespace).List( ctx, metav1.ListOptions{ LabelSelector: "app_id=" + appID, }, ) if err != nil { logrus.Errorf("list services for app %s error: %v", appID, err) return err } // 逐个删除 Service for _, svc := range serviceList.Items { if err := k8s.Default().Clientset.CoreV1().Services(namespace).Delete( ctx, svc.Name, metav1.DeleteOptions{}, ); err != nil { logrus.Warningf("delete service(%s): %v", svc.GetName(), err) } else { logrus.Infof("deleted service: %s", svc.GetName()) } } logrus.Infof("cleanup services for app %s in namespace %s completed", appID, namespace) return nil }