// Copyright (C) 2014-2018 Goodrain Co., Ltd. // RAINBOND, Application Management Platform // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. For any non-GPL usage of Rainbond, // one or multiple Commercial Licenses authorized by Goodrain Co., Ltd. // must be obtained first. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . package controller import ( "context" "encoding/json" "fmt" "io" "net/http" "os" "path" "path/filepath" "strconv" "strings" "github.com/go-chi/chi" "github.com/goodrain/rainbond-operator/api/v1alpha1" "github.com/goodrain/rainbond-operator/util/constants" "github.com/goodrain/rainbond/api/handler" "github.com/goodrain/rainbond/api/model" "github.com/goodrain/rainbond/builder/parser/code" "github.com/goodrain/rainbond/db" dbmodel "github.com/goodrain/rainbond/db/model" "github.com/goodrain/rainbond/pkg/component/k8s" utils "github.com/goodrain/rainbond/util" "github.com/jinzhu/gorm" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/types" k8sclient "sigs.k8s.io/controller-runtime/pkg/client" httputil "github.com/goodrain/rainbond/util/http" ) // ClusterController - type ClusterController struct { } // GetClusterInfo - func (c *ClusterController) GetClusterInfo(w http.ResponseWriter, r *http.Request) { nodes, err := handler.GetClusterHandler().GetClusterInfo(r.Context()) if err != nil { logrus.Errorf("get cluster info: %v", err) httputil.ReturnError(r, w, 500, err.Error()) return } httputil.ReturnSuccess(r, w, nodes) } // MavenSettingList maven setting list func (c *ClusterController) MavenSettingList(w http.ResponseWriter, r *http.Request) { httputil.ReturnSuccess(r, w, handler.GetClusterHandler().MavenSettingList(r.Context())) } // MavenSettingAdd maven setting add func (c *ClusterController) MavenSettingAdd(w http.ResponseWriter, r *http.Request) { var set handler.MavenSetting if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &set, nil); !ok { return } if err := handler.GetClusterHandler().MavenSettingAdd(r.Context(), &set); err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, &set) } // MavenSettingUpdate maven setting file update func (c *ClusterController) MavenSettingUpdate(w http.ResponseWriter, r *http.Request) { type SettingUpdate struct { Content string `json:"content" validate:"required"` } var su SettingUpdate if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &su, nil); !ok { return } set := &handler.MavenSetting{ Name: chi.URLParam(r, "name"), Content: su.Content, } if err := handler.GetClusterHandler().MavenSettingUpdate(r.Context(), set); err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, set) } // MavenSettingDelete maven setting file delete func (c *ClusterController) MavenSettingDelete(w http.ResponseWriter, r *http.Request) { err := handler.GetClusterHandler().MavenSettingDelete(r.Context(), chi.URLParam(r, "name")) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, nil) } // MavenSettingDetail maven setting file delete func (c *ClusterController) MavenSettingDetail(w http.ResponseWriter, r *http.Request) { setting, err := handler.GetClusterHandler().MavenSettingDetail(r.Context(), chi.URLParam(r, "name")) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, setting) } // BatchGetGateway batch get resource gateway func (c *ClusterController) BatchGetGateway(w http.ResponseWriter, r *http.Request) { ns, err := handler.GetClusterHandler().BatchGetGateway(r.Context()) if err != nil { logrus.Error(err) err.Handle(r, w) return } httputil.ReturnSuccess(r, w, ns) } // GetNamespace Get the unconnected namespaces under the current cluster func (c *ClusterController) GetNamespace(w http.ResponseWriter, r *http.Request) { content := r.FormValue("content") format := r.FormValue("format") if format == "detail" { ns, err := handler.GetClusterHandler().GetNamespaceDetail(r.Context(), content) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, ns) return } ns, err := handler.GetClusterHandler().GetNamespace(r.Context(), content) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, ns) } // GetNamespaceResource Get all resources in the current namespace func (c *ClusterController) GetNamespaceResource(w http.ResponseWriter, r *http.Request) { content := r.FormValue("content") namespace := r.FormValue("namespace") rs, err := handler.GetClusterHandler().GetNamespaceSource(r.Context(), content, namespace) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, rs) } // ConvertResource Get the resources under the current namespace to the rainbond platform func (c *ClusterController) ConvertResource(w http.ResponseWriter, r *http.Request) { content := r.FormValue("content") namespace := r.FormValue("namespace") rs, err := handler.GetClusterHandler().GetNamespaceSource(r.Context(), content, namespace) if err != nil { err.Handle(r, w) return } appsServices, err := handler.GetClusterHandler().ConvertResource(r.Context(), namespace, rs) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, appsServices) } // ResourceImport Import the converted k8s resources into recognition func (c *ClusterController) ResourceImport(w http.ResponseWriter, r *http.Request) { content := r.FormValue("content") namespace := r.FormValue("namespace") eid := r.FormValue("eid") rs, err := handler.GetClusterHandler().GetNamespaceSource(r.Context(), content, namespace) if err != nil { err.Handle(r, w) return } appsServices, err := handler.GetClusterHandler().ConvertResource(r.Context(), namespace, rs) if err != nil { err.Handle(r, w) return } rri, err := handler.GetClusterHandler().ResourceImport(namespace, appsServices, eid) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, rri) } // GetResource - func (c *ClusterController) GetResource(w http.ResponseWriter, r *http.Request) { var hr model.HandleResource if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &hr, nil); !ok { return } rri, err := handler.GetClusterHandler().GetAppK8SResource(r.Context(), hr.Namespace, hr.AppID, hr.Name, hr.ResourceYaml, hr.Kind) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, rri) } // AddResource - func (c *ClusterController) AddResource(w http.ResponseWriter, r *http.Request) { var hr model.AddHandleResource if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &hr, nil); !ok { return } rri, err := handler.GetClusterHandler().AddAppK8SResource(r.Context(), hr.Namespace, hr.AppID, hr.ResourceYaml) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, rri) } // UpdateResource - func (c *ClusterController) UpdateResource(w http.ResponseWriter, r *http.Request) { var hr model.HandleResource if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &hr, nil); !ok { return } rri, err := handler.GetClusterHandler().UpdateAppK8SResource(r.Context(), hr.Namespace, hr.AppID, hr.Name, hr.ResourceYaml, hr.Kind) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, rri) } // DeleteResource - func (c *ClusterController) DeleteResource(w http.ResponseWriter, r *http.Request) { var hr model.HandleResource if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &hr, nil); !ok { return } result, err := handler.GetClusterHandler().DeleteK8SResources(r.Context(), &model.K8sResourceDeletionRequest{ AppID: hr.AppID, CascadeCRD: hr.CascadeCRD, K8sResources: []model.HandleResource{hr}, }) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, result) } // PreviewDeleteResources returns the cluster-wide effect of deleting application resources. func (c *ClusterController) PreviewDeleteResources(w http.ResponseWriter, r *http.Request) { var req model.K8sResourceDeletionRequest if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil); !ok { return } impact, err := handler.GetClusterHandler().PreviewK8SResourceDeletion(r.Context(), &req) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, impact) } // BatchDeleteResource - func (c *ClusterController) BatchDeleteResource(w http.ResponseWriter, r *http.Request) { var req model.K8sResourceDeletionRequest if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil); !ok { return } result, err := handler.GetClusterHandler().DeleteK8SResources(r.Context(), &req) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, result) } // ReconcileResources removes metadata for resources confirmed absent from Kubernetes. func (c *ClusterController) ReconcileResources(w http.ResponseWriter, r *http.Request) { var req model.K8sResourceReconcileRequest if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil); !ok { return } result, err := handler.GetClusterHandler().ReconcileK8SResources(r.Context(), &req) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, result) } // SyncResource - func (c *ClusterController) SyncResource(w http.ResponseWriter, r *http.Request) { var req model.SyncResources if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil); !ok { return } resources, err := handler.GetClusterHandler().SyncAppK8SResources(r.Context(), &req) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, resources) } // YamlResourceName - func (c *ClusterController) YamlResourceName(w http.ResponseWriter, r *http.Request) { var yr model.YamlResource if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &yr, nil); !ok { return } h, err := handler.GetClusterHandler().AppYamlResourceName(yr) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, h) } // YamlResourceDetailed - func (c *ClusterController) YamlResourceDetailed(w http.ResponseWriter, r *http.Request) { var yr model.YamlResource if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &yr, nil); !ok { return } h, err := handler.GetClusterHandler().AppYamlResourceDetailed(yr, false) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, h) } // YamlResourceImport - func (c *ClusterController) YamlResourceImport(w http.ResponseWriter, r *http.Request) { var yr model.YamlResource if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &yr, nil); !ok { return } ar, err := handler.GetClusterHandler().AppYamlResourceDetailed(yr, true) if err != nil { err.Handle(r, w) return } ac, err := handler.GetClusterHandler().AppYamlResourceImport(yr.Namespace, yr.TenantID, yr.AppID, ar) if err != nil { err.Handle(r, w) return } httputil.ReturnSuccess(r, w, ac) } // CreateShellPod - func (c *ClusterController) CreateShellPod(w http.ResponseWriter, r *http.Request) { var sp model.ShellPod if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &sp, nil); !ok { return } pod, err := handler.GetClusterHandler().CreateShellPod(sp.RegionName) if err != nil { logrus.Error("create shell pod error:", err) return } httputil.ReturnSuccess(r, w, pod) } // DeleteShellPod - func (c *ClusterController) DeleteShellPod(w http.ResponseWriter, r *http.Request) { var sp model.ShellPod if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &sp, nil); !ok { return } err := handler.GetClusterHandler().DeleteShellPod(sp.PodName) if err != nil { logrus.Error("delete shell pod error:", err) return } httputil.ReturnSuccess(r, w, "") } // RbdLog - func (c *ClusterController) RbdLog(w http.ResponseWriter, r *http.Request) { podName := r.URL.Query().Get("pod_name") follow, _ := strconv.ParseBool(r.URL.Query().Get("follow")) err := handler.GetClusterHandler().RbdLog(w, r, podName, follow) if err != nil { httputil.ReturnBcodeError(r, w, err) return } return } // GetRbdPods - func (c *ClusterController) GetRbdPods(w http.ResponseWriter, r *http.Request) { res, err := handler.GetClusterHandler().GetRbdPods() if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, res) } // ListRainbondComponents - func (c *ClusterController) ListRainbondComponents(w http.ResponseWriter, r *http.Request) { components, err := handler.GetClusterHandler().ListRainbondComponents(r.Context()) if err != nil { logrus.Errorf("get rainbond components error: %v", err) httputil.ReturnError(r, w, 500, err.Error()) return } httputil.ReturnSuccess(r, w, components) } // Upgrade 自动升级集群端的镜像 func (c *ClusterController) Upgrade(w http.ResponseWriter, r *http.Request) { body := make(map[string]string) err := json.NewDecoder(r.Body).Decode(&body) if err != nil { httputil.ReturnBcodeError(r, w, err) return } res := make([]string, 0) for k, v := range body { var cpt v1alpha1.RbdComponent err := k8s.Default().K8sClient.Get(context.Background(), types.NamespacedName{Namespace: utils.GetenvDefault("RBD_NAMESPACE", constants.Namespace), Name: k}, &cpt) if err != nil { res = append(res, fmt.Sprintf(`%s获取异常%s`, k, err.Error())) continue } original := cpt.DeepCopy() cpt.Spec.Image = v logrus.Infof("upgrade [%s] image to [%s]", k, v) // Patch only the image so fields absent from the vendored CRD type are preserved. err = k8s.Default().K8sClient.Patch(context.Background(), &cpt, k8sclient.MergeFrom(original)) if err != nil { res = append(res, fmt.Sprintf(`%s更新异常%s`, k, err.Error())) continue } } httputil.ReturnSuccess(r, w, res) } // ListUpgradeStatus - func (c *ClusterController) ListUpgradeStatus(w http.ResponseWriter, r *http.Request) { status, err := handler.GetClusterHandler().ListUpgradeStatus() if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, status) } // ListPlugins - func (c *ClusterController) ListPlugins(w http.ResponseWriter, r *http.Request) { official, _ := strconv.ParseBool(r.URL.Query().Get("official")) res, err := handler.GetClusterHandler().ListPlugins(official) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, res) } // PluginExists is a lightweight probe that reports whether an official plugin // with the given name exists in the cluster. It avoids the heavy enrichment in // ListPlugins so callers that only need existence (e.g. edition detection) stay // fast even when the worker/status service is slow. func (c *ClusterController) PluginExists(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") exist, err := handler.GetClusterHandler().PluginExists(name) if err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, map[string]bool{"exist": exist}) } // CreateRBDPlugin creates an RBDPlugin CR func (c *ClusterController) CreateRBDPlugin(w http.ResponseWriter, r *http.Request) { var req model.CreateRBDPluginReq if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil); !ok { return } if err := handler.GetClusterHandler().CreatePlugin(&req); err != nil { httputil.ReturnBcodeError(r, w, err) return } httputil.ReturnSuccess(r, w, nil) } // ListAbilities - func (c *ClusterController) ListAbilities(w http.ResponseWriter, r *http.Request) { res, err := handler.GetClusterHandler().ListAbilities() if err != nil { httputil.ReturnError(r, w, 400, err.Error()) return } var abilities []model.AbilityResp for _, ability := range res { abilities = append(abilities, model.AbilityResp{ Name: ability.GetName(), Kind: ability.GetKind(), APIVersion: ability.GetAPIVersion(), AbilityID: handler.GetClusterHandler().GenerateAbilityID(&ability), }) } httputil.ReturnSuccess(r, w, abilities) } // GetAbility - func (c *ClusterController) GetAbility(w http.ResponseWriter, r *http.Request) { abilityID := chi.URLParam(r, "ability_id") res, err := handler.GetClusterHandler().GetAbility(abilityID) if err != nil { httputil.ReturnError(r, w, 400, err.Error()) return } httputil.ReturnSuccess(r, w, res) } // UpdateAbility - func (c *ClusterController) UpdateAbility(w http.ResponseWriter, r *http.Request) { abilityID := chi.URLParam(r, "ability_id") var req model.UpdateAbilityReq if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil); !ok { return } if err := handler.GetClusterHandler().UpdateAbility(abilityID, req.Object); err != nil { httputil.ReturnError(r, w, 400, err.Error()) return } httputil.ReturnSuccess(r, w, nil) } // GetLangVersion Get the unconnected namespaces under the current cluster func (c *ClusterController) GetLangVersion(w http.ResponseWriter, r *http.Request) { // language:查询的语言 // show:版本信息 language := r.URL.Query().Get("language") show := r.URL.Query().Get("show") buildStrategy := normalizeLongVersionBuildStrategy(r.URL.Query().Get("build_strategy")) // 获取版本列表 versions, err := db.GetManager().LongVersionDao().ListVersionByLanguageAndStrategy(language, show, buildStrategy) if err != nil { httputil.ReturnBcodeError(r, w, fmt.Errorf("update lang version failure: %v", err)) return } httputil.ReturnSuccess(r, w, versions) } // UpdateLangVersion - func (c *ClusterController) UpdateLangVersion(w http.ResponseWriter, r *http.Request) { var lang model.UpdateLangVersion if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &lang, nil); !ok { httputil.ReturnError(r, w, 400, "failed to parse parameters") return } existing, err := getExistingLongVersion(&lang) if err != nil { httputil.ReturnError(r, w, 400, fmt.Sprintf("get lang version failure: %v", err)) return } updatedVersion := buildLangVersionForUpdate(existing, &lang) // 更新默认语言版本 result, err := db.GetManager().LongVersionDao().DefaultLangVersion(updatedVersion.Lang, updatedVersion.Version, updatedVersion.BuildStrategy, updatedVersion.Show, updatedVersion.FirstChoice, &updatedVersion.IsAllowed) if err != nil { httputil.ReturnError(r, w, 400, fmt.Sprintf("update lang version failure: %v", err)) return } httputil.ReturnSuccess(r, w, result) } // CreateLangVersion - func (c *ClusterController) CreateLangVersion(w http.ResponseWriter, r *http.Request) { var lang model.UpdateLangVersion if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &lang, nil); !ok { httputil.ReturnError(r, w, 400, "failed to parse parameters") return } newVersion := buildLangVersionForCreate(&lang) // 根据语言标识和版本号,获取语言版本信息。 existing, err := db.GetManager().LongVersionDao().GetVersionByLanguageAndVersionAndStrategy(newVersion.Lang, newVersion.Version, newVersion.BuildStrategy) if err != nil && err != gorm.ErrRecordNotFound { httputil.ReturnError(r, w, 400, fmt.Sprintf("get lang version failure: %v", err)) return } if err == gorm.ErrRecordNotFound { // 创建新的语言版本记录。 created, err := db.GetManager().LongVersionDao().CreateLangVersion(newVersion.Lang, newVersion.Version, newVersion.EventID, newVersion.FileName, newVersion.BuildStrategy, newVersion.Show, newVersion.IsAllowed) if err != nil { httputil.ReturnError(r, w, 400, fmt.Sprintf("create lang version failure: %v", err)) return } if created.EventID != "" { sourceDir := path.Join(LSUploadPath, created.EventID) destinationDir := path.Join(BaseUploadPath, created.EventID) err = copyDirectory(sourceDir, destinationDir) if err != nil { httputil.ReturnError(r, w, 400, fmt.Sprintf("rename lang version failure: %v", err)) return } } httputil.ReturnSuccess(r, w, created) return } _ = existing httputil.ReturnSuccess(r, w, "exist") return } // DeleteLangVersion - func (c *ClusterController) DeleteLangVersion(w http.ResponseWriter, r *http.Request) { var lang model.UpdateLangVersion if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &lang, nil); !ok { httputil.ReturnError(r, w, 400, "failed to parse parameters") return } // 根据语言标识和版本号,删除该语言版本 eventID, err := db.GetManager().LongVersionDao().DeleteLangVersion(lang.Lang, lang.Version, normalizeLongVersionBuildStrategyPtr(lang.BuildStrategy)) if err != nil { httputil.ReturnError(r, w, 400, fmt.Sprintf("delete lang version failure: %v", err)) return } // 删除本地文件 if eventID != "" { err = os.RemoveAll(path.Join(BaseUploadPath, eventID)) if err != nil { httputil.ReturnError(r, w, 400, fmt.Sprintf("delete lang version pack failure: %v", err)) return } } httputil.ReturnSuccess(r, w, "删除成功") } func getExistingLongVersion(req *model.UpdateLangVersion) (*dbmodel.EnterpriseLanguageVersion, error) { if req.BuildStrategy != nil { return db.GetManager().LongVersionDao().GetVersionByLanguageAndVersionAndStrategy(req.Lang, req.Version, normalizeLongVersionBuildStrategyPtr(req.BuildStrategy)) } return db.GetManager().LongVersionDao().GetVersionByLanguageAndVersion(req.Lang, req.Version) } func buildLangVersionForCreate(req *model.UpdateLangVersion) *dbmodel.EnterpriseLanguageVersion { return &dbmodel.EnterpriseLanguageVersion{ Lang: req.Lang, Version: req.Version, EventID: req.EventID, FileName: req.FileName, Show: req.Show, FirstChoice: req.FirstChoice, BuildStrategy: normalizeLongVersionBuildStrategyPtr(req.BuildStrategy), IsAllowed: normalizeLongVersionIsAllowed(req.IsAllowed, true), } } func buildLangVersionForUpdate(existing *dbmodel.EnterpriseLanguageVersion, req *model.UpdateLangVersion) *dbmodel.EnterpriseLanguageVersion { version := *existing version.Show = req.Show version.FirstChoice = req.FirstChoice version.BuildStrategy = normalizeLongVersionBuildStrategy(version.BuildStrategy) if req.BuildStrategy != nil { version.BuildStrategy = normalizeLongVersionBuildStrategyPtr(req.BuildStrategy) } version.IsAllowed = normalizeLongVersionIsAllowed(req.IsAllowed, version.IsAllowed) return &version } func normalizeLongVersionBuildStrategyPtr(buildStrategy *string) string { if buildStrategy == nil { return dbmodel.LongVersionBuildStrategySlug } return normalizeLongVersionBuildStrategy(*buildStrategy) } func normalizeLongVersionBuildStrategy(buildStrategy string) string { buildStrategy = strings.TrimSpace(buildStrategy) if buildStrategy == "" { return dbmodel.LongVersionBuildStrategySlug } return buildStrategy } func normalizeLongVersionIsAllowed(isAllowed *bool, defaultValue bool) bool { if isAllowed == nil { return defaultValue } return *isAllowed } func copyFile(src, dst string) error { sourceFile, err := os.Open(src) if err != nil { return err } defer sourceFile.Close() destinationFile, err := os.Create(dst) if err != nil { return err } defer destinationFile.Close() _, err = io.Copy(destinationFile, sourceFile) if err != nil { return err } err = destinationFile.Sync() if err != nil { return err } return nil } func copyDirectory(srcDir, dstDir string) error { err := os.MkdirAll(dstDir, os.ModePerm) if err != nil { return err } err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } relativePath, err := filepath.Rel(srcDir, path) if err != nil { return err } dstPath := filepath.Join(dstDir, relativePath) if info.IsDir() { return os.MkdirAll(dstPath, info.Mode()) } if info.Mode()&os.ModeSymlink != 0 { return fmt.Errorf("skipping symbolic link: %s", path) } return copyFile(path, dstPath) }) return err } // GetRegionStatus returns the region status after validating the Helm request. func (c *ClusterController) GetRegionStatus(w http.ResponseWriter, r *http.Request) { token := chi.URLParam(r, "token") if token != os.Getenv("HELM_TOKEN") { httputil.ReturnError(r, w, 400, "failed to verify token") return } regionInfo, err := handler.GetClusterHandler().GetClusterRegionStatus() if err != nil { httputil.ReturnError(r, w, 400, err.Error()) return } httputil.ReturnSuccess(r, w, regionInfo) } // SetOverScore updates the cluster resource overcommit rate. func (c *ClusterController) SetOverScore(w http.ResponseWriter, r *http.Request) { var overScore model.OverScore if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &overScore, nil); !ok { httputil.ReturnError(r, w, 400, "failed to parse parameters") return } // 根据语言标识和版本号,获取语言版本信息。 err := db.GetManager().OverScoreDao().UpdateOverScoreRat(overScore.OverScoreRate) if err != nil { httputil.ReturnError(r, w, 400, fmt.Sprintf("update over score failure: %v", err)) return } return } // ListCNBFrameworks returns the supported CNB build frameworks for a given language. func (c *ClusterController) ListCNBFrameworks(w http.ResponseWriter, r *http.Request) { lang := r.URL.Query().Get("lang") if lang == "" { lang = "nodejs" } frameworks := code.GetSupportedFrameworks(lang) httputil.ReturnSuccess(r, w, frameworks) } // RegionReadiness returns whether the region API is ready to serve requests. // The console can probe this endpoint to detect unavailability before making // API calls, receiving structured errors instead of raw stack traces. func (c *ClusterController) RegionReadiness(w http.ResponseWriter, r *http.Request) { result, err := handler.GetClusterHandler().CheckReadiness(r.Context()) if err != nil { httputil.ReturnError(r, w, 500, fmt.Sprintf("readiness check failed: %v", err)) return } if !result.Ready { httputil.ReturnError(r, w, 503, result.Message) return } httputil.ReturnSuccess(r, w, result) } // BootstrapAgentKubeconfig creates a ServiceAccount-backed kubeconfig for rainbond-agent. func (c *ClusterController) BootstrapAgentKubeconfig(w http.ResponseWriter, r *http.Request) { var req handler.AgentKubeconfigBootstrapRequest if ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil); !ok { return } result, err := handler.GetClusterHandler().BootstrapAgentKubeconfig(r.Context(), &req) if err != nil { returnAgentKubeconfigBootstrapError(r, w, err) return } httputil.ReturnSuccess(r, w, result) } func returnAgentKubeconfigBootstrapError(r *http.Request, w http.ResponseWriter, err error) { if _, ok := err.(httputil.ErrBadRequest); ok { httputil.ReturnError(r, w, http.StatusBadRequest, err.Error()) return } httputil.ReturnError(r, w, http.StatusInternalServerError, err.Error()) }