From c79a04900ddeeaa1e9d83ce5de71f9bd8dfb79cc Mon Sep 17 00:00:00 2001 From: Ashish-devtron Date: Fri, 17 Feb 2023 20:36:03 +0530 Subject: [PATCH 1/7] helm-scale-workload --- .../deployment/service/InstalledAppService.go | 56 +++++++++++++++++++ util/helper.go | 28 ++++++++++ 2 files changed, 84 insertions(+) diff --git a/pkg/appStore/deployment/service/InstalledAppService.go b/pkg/appStore/deployment/service/InstalledAppService.go index 09535be222..7d3075300d 100644 --- a/pkg/appStore/deployment/service/InstalledAppService.go +++ b/pkg/appStore/deployment/service/InstalledAppService.go @@ -39,6 +39,7 @@ import ( util2 "github.com/devtron-labs/devtron/pkg/util" util3 "github.com/devtron-labs/devtron/util" "github.com/devtron-labs/devtron/util/argo" + "github.com/tidwall/gjson" "net/http" /* #nosec */ @@ -925,6 +926,7 @@ func (impl InstalledAppServiceImpl) FetchResourceTree(rctx context.Context, cn h } // TODO: using this resp.Status to update in app_status table appDetail.ResourceTree = util3.InterfaceToMapAdapter(resp) + appDetail.ResourceTree = checkHibernate(impl, appDetail, ctx) err = impl.appStatusService.UpdateStatusWithAppIdEnvId(appDetail.AppId, appDetail.EnvironmentId, resp.Status) if err != nil { impl.logger.Warnw("error in updating app status", "err", err, appDetail.AppId, "envId", appDetail.EnvironmentId) @@ -955,3 +957,57 @@ func (impl InstalledAppServiceImpl) FetchResourceTree(rctx context.Context, cn h } return *appDetail } + +func checkHibernate(impl InstalledAppServiceImpl, resp *bean2.AppDetailContainer, ctx context.Context) map[string]interface{} { + + responseTree := resp.ResourceTree + + for _, node := range responseTree["nodes"].(interface{}).([]interface{}) { + currNode := node.(interface{}).(map[string]interface{}) + name := resp.AppName + "-" + resp.Namespace + resName := util3.InterfaceToString(currNode["name"]) + resKind := util3.InterfaceToString(currNode["kind"]) + resGroup := util3.InterfaceToString(currNode["group"]) + resVersion := util3.InterfaceToString(currNode["version"]) + resNamespace := util3.InterfaceToString(currNode["namespace"]) + rQuery := &application.ApplicationResourceRequest{ + Name: &name, + ResourceName: &resName, + Kind: &resKind, + Group: &resGroup, + Version: &resVersion, + Namespace: &resNamespace, + } + ctx, _ := context.WithTimeout(ctx, 60*time.Second) + if currNode["parentRefs"] == nil { + + res, err := impl.acdClient.GetResource(ctx, rQuery) + if res.Manifest != nil { + manifest, _ := gjson.Parse(*res.Manifest).Value().(map[string]interface{}) + if err != nil { + impl.logger.Errorw("GRPC_GET_RESOURCE", "data", res, "timeTaken", time.Since(time.Now()), "err", err) + } + replicas := util3.InterfaceToMapAdapter(manifest["spec"])["replicas"] + if replicas != nil { + currNode["canBeHibernated"] = true + } + annotations := util3.InterfaceToMapAdapter(manifest["metadata"])["annotations"] + if annotations != nil { + val := util3.InterfaceToMapAdapter(annotations)["hibernator.devtron.ai/replicas"] + if val != nil { + if util3.InterfaceToString(val) != "0" && util3.InterfaceToFloat(replicas) == 0 { + currNode["isHibernated"] = true + } + } + } + + } + + if err != nil { + impl.logger.Errorw("GRPC_GET_RESOURCE", "data", res, "timeTaken", time.Since(time.Now()), "err", err) + } + } + node = currNode + } + return responseTree +} diff --git a/util/helper.go b/util/helper.go index 8563783fc8..ad30f00e5b 100644 --- a/util/helper.go +++ b/util/helper.go @@ -208,3 +208,31 @@ func InterfaceToMapAdapter(resp interface{}) map[string]interface{} { } return dat } + +func InterfaceToString(resp interface{}) string { + var dat string + b, err := json.Marshal(resp) + if err != nil { + fmt.Printf("Error: %s", err) + return dat + } + if err := json.Unmarshal(b, &dat); err != nil { + fmt.Printf("Error: %s", err) + return dat + } + return dat +} + +func InterfaceToFloat(resp interface{}) float64 { + var dat float64 + b, err := json.Marshal(resp) + if err != nil { + fmt.Printf("Error: %s", err) + return dat + } + if err := json.Unmarshal(b, &dat); err != nil { + fmt.Printf("Error: %s", err) + return dat + } + return dat +} From 6d22221fac7a8542c39bbd93bd042f7f14fd5a26 Mon Sep 17 00:00:00 2001 From: Ashish-devtron Date: Mon, 27 Feb 2023 13:32:25 +0530 Subject: [PATCH 2/7] check error --- pkg/appStore/deployment/service/InstalledAppService.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/appStore/deployment/service/InstalledAppService.go b/pkg/appStore/deployment/service/InstalledAppService.go index 7d3075300d..20b1056e5f 100644 --- a/pkg/appStore/deployment/service/InstalledAppService.go +++ b/pkg/appStore/deployment/service/InstalledAppService.go @@ -982,11 +982,12 @@ func checkHibernate(impl InstalledAppServiceImpl, resp *bean2.AppDetailContainer if currNode["parentRefs"] == nil { res, err := impl.acdClient.GetResource(ctx, rQuery) + if err != nil { + impl.logger.Errorw("GRPC_GET_RESOURCE", "data", res, "timeTaken", time.Since(time.Now()), "err", err) + return responseTree + } if res.Manifest != nil { manifest, _ := gjson.Parse(*res.Manifest).Value().(map[string]interface{}) - if err != nil { - impl.logger.Errorw("GRPC_GET_RESOURCE", "data", res, "timeTaken", time.Since(time.Now()), "err", err) - } replicas := util3.InterfaceToMapAdapter(manifest["spec"])["replicas"] if replicas != nil { currNode["canBeHibernated"] = true From 6b5a5d42dfb0fec1a3af19e4283507799dc78e85 Mon Sep 17 00:00:00 2001 From: Ashish-devtron Date: Thu, 9 Mar 2023 19:12:40 +0530 Subject: [PATCH 3/7] separate-hibernate --- api/appStore/AppStoreRouter.go | 3 + api/appStore/InstalledAppRestHandler.go | 62 ++++++++++ .../deployment/service/InstalledAppService.go | 114 +++++++++++------- 3 files changed, 136 insertions(+), 43 deletions(-) diff --git a/api/appStore/AppStoreRouter.go b/api/appStore/AppStoreRouter.go index a3aa657a8d..d0fa9de71b 100644 --- a/api/appStore/AppStoreRouter.go +++ b/api/appStore/AppStoreRouter.go @@ -73,4 +73,7 @@ func (router AppStoreRouterImpl) Init(configRouter *mux.Router) { HandlerFunc(router.deployRestHandler.GetAllInstalledApp).Methods("GET") configRouter.Path("/cluster-component/install/{clusterId}"). HandlerFunc(router.deployRestHandler.DefaultComponentInstallation).Methods("POST") + configRouter.Path("/installed-app/resource/hibernate").Queries("installed-app-id", "{installed-app-id}").Queries("env-id", "{env-id}"). + HandlerFunc(router.deployRestHandler.FetchResourceTreeForACDApp). + Methods("GET") } diff --git a/api/appStore/InstalledAppRestHandler.go b/api/appStore/InstalledAppRestHandler.go index 88b2d81807..cad00d5f1b 100644 --- a/api/appStore/InstalledAppRestHandler.go +++ b/api/appStore/InstalledAppRestHandler.go @@ -48,6 +48,7 @@ type InstalledAppRestHandler interface { CheckAppExists(w http.ResponseWriter, r *http.Request) DefaultComponentInstallation(w http.ResponseWriter, r *http.Request) FetchAppDetailsForInstalledApp(w http.ResponseWriter, r *http.Request) + FetchResourceTreeForACDApp(w http.ResponseWriter, r *http.Request) } type InstalledAppRestHandlerImpl struct { @@ -433,8 +434,69 @@ func (handler *InstalledAppRestHandlerImpl) FetchAppDetailsForInstalledApp(w htt common.WriteJsonResp(w, err, appDetail, http.StatusOK) } +func (handler *InstalledAppRestHandlerImpl) FetchResourceTreeForACDApp(w http.ResponseWriter, r *http.Request) { + userId, err := handler.userAuthService.GetLoggedInUser(r) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, nil, http.StatusUnauthorized) + return + } + + vars := mux.Vars(r) + installedAppId, err := strconv.Atoi(vars["installed-app-id"]) + if err != nil { + handler.Logger.Errorw("request err, FetchAppDetailsForInstalledApp", "err", err, "installedAppId", installedAppId) + common.WriteJsonResp(w, err, nil, http.StatusBadRequest) + return + } + token := r.Header.Get("token") + envId, err := strconv.Atoi(vars["env-id"]) + if err != nil { + handler.Logger.Errorw("request err, FetchAppDetailsForInstalledApp", "err", err, "installedAppId", installedAppId, "envId", envId) + common.WriteJsonResp(w, err, nil, http.StatusBadRequest) + return + } + handler.Logger.Infow("request payload, FetchAppDetailsForInstalledApp, app store", "installedAppId", installedAppId, "envId", envId) + + appDetail, err := handler.installedAppService.FindAppDetailsForAppstoreApplication(installedAppId, envId) + if err != nil { + handler.Logger.Errorw("service err, FetchAppDetailsForInstalledApp, app store", "err", err, "installedAppId", installedAppId, "envId", envId) + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + return + } + + //rbac block starts from here + object, object2 := handler.enforcerUtil.GetHelmObjectByAppNameAndEnvId(appDetail.AppName, appDetail.EnvironmentId) + + var ok bool + + if object2 == "" { + ok = handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, object) + } else { + ok = handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, object) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, object2) + } + + if !ok { + common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), nil, http.StatusForbidden) + return + } + //rback block ends here + if len(appDetail.AppName) > 0 && len(appDetail.EnvironmentName) > 0 { + handler.fetchResourceTreeWithHibernateForACD(w, r, &appDetail) + } else { + appDetail.ResourceTree = map[string]interface{}{} + handler.Logger.Warnw("appName and envName not found - avoiding resource tree call", "app", appDetail.AppName, "env", appDetail.EnvironmentName) + } + common.WriteJsonResp(w, err, appDetail, http.StatusOK) +} + func (handler *InstalledAppRestHandlerImpl) fetchResourceTree(w http.ResponseWriter, r *http.Request, appDetail *bean2.AppDetailContainer) { ctx := r.Context() cn, _ := w.(http.CloseNotifier) handler.installedAppService.FetchResourceTree(ctx, cn, appDetail) } + +func (handler *InstalledAppRestHandlerImpl) fetchResourceTreeWithHibernateForACD(w http.ResponseWriter, r *http.Request, appDetail *bean2.AppDetailContainer) { + ctx := r.Context() + cn, _ := w.(http.CloseNotifier) + handler.installedAppService.FetchResourceTreeWithHibernateForACD(ctx, cn, appDetail) +} diff --git a/pkg/appStore/deployment/service/InstalledAppService.go b/pkg/appStore/deployment/service/InstalledAppService.go index 20b1056e5f..c96914dd4b 100644 --- a/pkg/appStore/deployment/service/InstalledAppService.go +++ b/pkg/appStore/deployment/service/InstalledAppService.go @@ -80,6 +80,7 @@ type InstalledAppService interface { FindAppDetailsForAppstoreApplication(installedAppId, envId int) (bean2.AppDetailContainer, error) UpdateInstalledAppVersionStatus(application *v1alpha1.Application) (bool, error) FetchResourceTree(rctx context.Context, cn http.CloseNotifier, appDetail *bean2.AppDetailContainer) bean2.AppDetailContainer + FetchResourceTreeWithHibernateForACD(rctx context.Context, cn http.CloseNotifier, appDetail *bean2.AppDetailContainer) bean2.AppDetailContainer } type InstalledAppServiceImpl struct { @@ -887,51 +888,10 @@ func (impl InstalledAppServiceImpl) GetInstalledAppVersionHistoryValues(installe values.ValuesYaml = versionHistory.ValuesYamlRaw return values, err } + func (impl InstalledAppServiceImpl) FetchResourceTree(rctx context.Context, cn http.CloseNotifier, appDetail *bean2.AppDetailContainer) bean2.AppDetailContainer { if util.IsAcdApp(appDetail.DeploymentAppType) { - acdAppName := appDetail.AppName + "-" + appDetail.EnvironmentName - query := &application.ResourcesQuery{ - ApplicationName: &acdAppName, - } - ctx, cancel := context.WithCancel(rctx) - if cn != nil { - go func(done <-chan struct{}, closed <-chan bool) { - select { - case <-done: - case <-closed: - cancel() - } - }(ctx.Done(), cn.CloseNotify()) - } - acdToken, err := impl.argoUserService.GetLatestDevtronArgoCdUserToken() - if err != nil { - impl.logger.Errorw("error in getting acd token", "err", err) - return *appDetail - } - ctx = context.WithValue(ctx, "token", acdToken) - defer cancel() - start := time.Now() - resp, err := impl.acdClient.ResourceTree(ctx, query) - elapsed := time.Since(start) - impl.logger.Debugf("Time elapsed %s in fetching app-store installed application %s for environment %s", elapsed, appDetail.InstalledAppId, appDetail.EnvironmentId) - if err != nil { - impl.logger.Errorw("service err, FetchAppDetailsForInstalledApp, fetching resource tree", "err", err, "installedAppId", appDetail.InstalledAppId, "envId", appDetail.EnvironmentId) - err = &util.ApiError{ - Code: constants.AppDetailResourceTreeNotFound, - InternalMessage: "app detail fetched, failed to get resource tree from acd", - UserMessage: "app detail fetched, failed to get resource tree from acd", - } - appDetail.ResourceTree = map[string]interface{}{} - return *appDetail - } - // TODO: using this resp.Status to update in app_status table - appDetail.ResourceTree = util3.InterfaceToMapAdapter(resp) - appDetail.ResourceTree = checkHibernate(impl, appDetail, ctx) - err = impl.appStatusService.UpdateStatusWithAppIdEnvId(appDetail.AppId, appDetail.EnvironmentId, resp.Status) - if err != nil { - impl.logger.Warnw("error in updating app status", "err", err, appDetail.AppId, "envId", appDetail.EnvironmentId) - } - impl.logger.Debugf("application %s in environment %s had status %+v\n", appDetail.InstalledAppId, appDetail.EnvironmentId, resp) + appDetail = fetchResourceTreeForACD(rctx, cn, appDetail, impl) } else if util.IsHelmApp(appDetail.DeploymentAppType) { config, err := impl.helmAppService.GetClusterConf(appDetail.ClusterId) if err != nil { @@ -958,6 +918,28 @@ func (impl InstalledAppServiceImpl) FetchResourceTree(rctx context.Context, cn h return *appDetail } +func (impl InstalledAppServiceImpl) FetchResourceTreeWithHibernateForACD(rctx context.Context, cn http.CloseNotifier, appDetail *bean2.AppDetailContainer) bean2.AppDetailContainer { + ctx, cancel := context.WithCancel(rctx) + if cn != nil { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + acdToken, err := impl.argoUserService.GetLatestDevtronArgoCdUserToken() + if err != nil { + impl.logger.Errorw("error in getting acd token", "err", err) + return *appDetail + } + ctx = context.WithValue(ctx, "token", acdToken) + defer cancel() + appDetail = fetchResourceTreeForACD(rctx, cn, appDetail, impl) + appDetail.ResourceTree = checkHibernate(impl, appDetail, ctx) + return *appDetail +} func checkHibernate(impl InstalledAppServiceImpl, resp *bean2.AppDetailContainer, ctx context.Context) map[string]interface{} { responseTree := resp.ResourceTree @@ -1012,3 +994,49 @@ func checkHibernate(impl InstalledAppServiceImpl, resp *bean2.AppDetailContainer } return responseTree } + +func fetchResourceTreeForACD(rctx context.Context, cn http.CloseNotifier, appDetail *bean2.AppDetailContainer, impl InstalledAppServiceImpl) *bean2.AppDetailContainer { + acdAppName := appDetail.AppName + "-" + appDetail.EnvironmentName + query := &application.ResourcesQuery{ + ApplicationName: &acdAppName, + } + ctx, cancel := context.WithCancel(rctx) + if cn != nil { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + acdToken, err := impl.argoUserService.GetLatestDevtronArgoCdUserToken() + if err != nil { + impl.logger.Errorw("error in getting acd token", "err", err) + return appDetail + } + ctx = context.WithValue(ctx, "token", acdToken) + defer cancel() + start := time.Now() + resp, err := impl.acdClient.ResourceTree(ctx, query) + elapsed := time.Since(start) + impl.logger.Debugf("Time elapsed %s in fetching app-store installed application %s for environment %s", elapsed, appDetail.InstalledAppId, appDetail.EnvironmentId) + if err != nil { + impl.logger.Errorw("service err, FetchAppDetailsForInstalledApp, fetching resource tree", "err", err, "installedAppId", appDetail.InstalledAppId, "envId", appDetail.EnvironmentId) + err = &util.ApiError{ + Code: constants.AppDetailResourceTreeNotFound, + InternalMessage: "app detail fetched, failed to get resource tree from acd", + UserMessage: "app detail fetched, failed to get resource tree from acd", + } + appDetail.ResourceTree = map[string]interface{}{} + return appDetail + } + // TODO: using this resp.Status to update in app_status table + appDetail.ResourceTree = util3.InterfaceToMapAdapter(resp) + err = impl.appStatusService.UpdateStatusWithAppIdEnvId(appDetail.AppId, appDetail.EnvironmentId, resp.Status) + if err != nil { + impl.logger.Warnw("error in updating app status", "err", err, appDetail.AppId, "envId", appDetail.EnvironmentId) + } + impl.logger.Debugf("application %s in environment %s had status %+v\n", appDetail.InstalledAppId, appDetail.EnvironmentId, resp) + return appDetail +} From 12c2f0983687c7c701e444fcededd7c54ceeb702 Mon Sep 17 00:00:00 2001 From: Ashish-devtron Date: Thu, 9 Mar 2023 20:16:46 +0530 Subject: [PATCH 4/7] code-refactor --- api/appStore/AppStoreRouter.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/appStore/AppStoreRouter.go b/api/appStore/AppStoreRouter.go index d0fa9de71b..43d67e840b 100644 --- a/api/appStore/AppStoreRouter.go +++ b/api/appStore/AppStoreRouter.go @@ -69,11 +69,12 @@ func (router AppStoreRouterImpl) Init(configRouter *mux.Router) { configRouter.Path("/installed-app/detail").Queries("installed-app-id", "{installed-app-id}").Queries("env-id", "{env-id}"). HandlerFunc(router.deployRestHandler.FetchAppDetailsForInstalledApp). Methods("GET") + configRouter.Path("/installed-app/resource/hibernate").Queries("installed-app-id", "{installed-app-id}").Queries("env-id", "{env-id}"). + HandlerFunc(router.deployRestHandler.FetchResourceTreeForACDApp). + Methods("GET") configRouter.Path("/installed-app"). HandlerFunc(router.deployRestHandler.GetAllInstalledApp).Methods("GET") configRouter.Path("/cluster-component/install/{clusterId}"). HandlerFunc(router.deployRestHandler.DefaultComponentInstallation).Methods("POST") - configRouter.Path("/installed-app/resource/hibernate").Queries("installed-app-id", "{installed-app-id}").Queries("env-id", "{env-id}"). - HandlerFunc(router.deployRestHandler.FetchResourceTreeForACDApp). - Methods("GET") + } From 2447af9c8388937b01950e677df10534feed054b Mon Sep 17 00:00:00 2001 From: Ashish-devtron Date: Mon, 13 Mar 2023 22:22:42 +0530 Subject: [PATCH 5/7] get-hibernate-resource-tree --- api/appStore/AppStoreRouter.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/appStore/AppStoreRouter.go b/api/appStore/AppStoreRouter.go index 43d67e840b..575ab519ec 100644 --- a/api/appStore/AppStoreRouter.go +++ b/api/appStore/AppStoreRouter.go @@ -76,5 +76,4 @@ func (router AppStoreRouterImpl) Init(configRouter *mux.Router) { HandlerFunc(router.deployRestHandler.GetAllInstalledApp).Methods("GET") configRouter.Path("/cluster-component/install/{clusterId}"). HandlerFunc(router.deployRestHandler.DefaultComponentInstallation).Methods("POST") - } From 228411fcbae14c162d0467ac487ffae5256c1cfc Mon Sep 17 00:00:00 2001 From: Ashish-devtron Date: Mon, 20 Mar 2023 13:26:34 +0530 Subject: [PATCH 6/7] scale-helm-workload --- .../sql/repository/GlobalCMCSRepository.go | 6 +- .../deployment/service/InstalledAppService.go | 171 +++++++++++++----- pkg/chartRepo/ChartRepositoryService_test.go | 4 +- .../UserTerminalAccessService.go | 2 +- pkg/pipeline/PipelineStageService.go | 10 +- util/k8s/k8sCapacityService.go | 2 +- 6 files changed, 140 insertions(+), 55 deletions(-) diff --git a/internal/sql/repository/GlobalCMCSRepository.go b/internal/sql/repository/GlobalCMCSRepository.go index 78c758f444..e87fd8b057 100644 --- a/internal/sql/repository/GlobalCMCSRepository.go +++ b/internal/sql/repository/GlobalCMCSRepository.go @@ -47,9 +47,9 @@ type GlobalCMCS struct { Type string `sql:"type"` // [environment, volume] //json string of map of key:value, example: '{ "a" : "b", "c" : "d"}' Data string `sql:"data"` - MountPath string `sql:"mount_path"` - Deleted bool `sql:"deleted,notnull"` - SecretIngestionFor string `sql:"secret_ingestion_for,notnull"` // [CI, CD, CI/CD] + MountPath string `sql:"mount_path"` + Deleted bool `sql:"deleted,notnull"` + SecretIngestionFor string `sql:"secret_ingestion_for,notnull"` // [CI, CD, CI/CD] sql.AuditLog } diff --git a/pkg/appStore/deployment/service/InstalledAppService.go b/pkg/appStore/deployment/service/InstalledAppService.go index 7685288158..b1240dea7d 100644 --- a/pkg/appStore/deployment/service/InstalledAppService.go +++ b/pkg/appStore/deployment/service/InstalledAppService.go @@ -39,6 +39,7 @@ import ( util2 "github.com/devtron-labs/devtron/pkg/util" util3 "github.com/devtron-labs/devtron/util" "github.com/devtron-labs/devtron/util/argo" + "github.com/tidwall/gjson" "net/http" /* #nosec */ @@ -82,6 +83,7 @@ type InstalledAppService interface { MarkGitOpsInstalledAppsDeletedIfArgoAppIsDeleted(installedAppId int, envId int) error CheckAppExistsByInstalledAppId(installedAppId int) error FindNotesForArgoApplication(installedAppId, envId int) (string, string, error) + FetchResourceTreeWithHibernateForACD(rctx context.Context, cn http.CloseNotifier, appDetail *bean2.AppDetailContainer) bean2.AppDetailContainer } type InstalledAppServiceImpl struct { @@ -934,49 +936,9 @@ func (impl InstalledAppServiceImpl) GetInstalledAppVersionHistoryValues(installe return values, err } func (impl InstalledAppServiceImpl) FetchResourceTree(rctx context.Context, cn http.CloseNotifier, appDetail *bean2.AppDetailContainer) (bean2.AppDetailContainer, error) { + var err error if util.IsAcdApp(appDetail.DeploymentAppType) { - acdAppName := appDetail.AppName + "-" + appDetail.EnvironmentName - query := &application.ResourcesQuery{ - ApplicationName: &acdAppName, - } - ctx, cancel := context.WithCancel(rctx) - if cn != nil { - go func(done <-chan struct{}, closed <-chan bool) { - select { - case <-done: - case <-closed: - cancel() - } - }(ctx.Done(), cn.CloseNotify()) - } - acdToken, err := impl.argoUserService.GetLatestDevtronArgoCdUserToken() - if err != nil { - impl.logger.Errorw("error in getting acd token", "err", err) - return *appDetail, err - } - ctx = context.WithValue(ctx, "token", acdToken) - defer cancel() - start := time.Now() - resp, err := impl.acdClient.ResourceTree(ctx, query) - elapsed := time.Since(start) - impl.logger.Debugf("Time elapsed %s in fetching app-store installed application %s for environment %s", elapsed, appDetail.InstalledAppId, appDetail.EnvironmentId) - if err != nil { - impl.logger.Errorw("service err, FetchAppDetailsForInstalledApp, fetching resource tree", "err", err, "installedAppId", appDetail.InstalledAppId, "envId", appDetail.EnvironmentId) - err = &util.ApiError{ - Code: constants.AppDetailResourceTreeNotFound, - InternalMessage: "app detail fetched, failed to get resource tree from acd", - UserMessage: "app detail fetched, failed to get resource tree from acd", - } - appDetail.ResourceTree = map[string]interface{}{} - return *appDetail, err - } - // TODO: using this resp.Status to update in app_status table - appDetail.ResourceTree = util3.InterfaceToMapAdapter(resp) - err = impl.appStatusService.UpdateStatusWithAppIdEnvId(appDetail.AppId, appDetail.EnvironmentId, resp.Status) - if err != nil { - impl.logger.Warnw("error in updating app status", "err", err, appDetail.AppId, "envId", appDetail.EnvironmentId) - } - impl.logger.Debugf("application %s in environment %s had status %+v\n", appDetail.InstalledAppId, appDetail.EnvironmentId, resp) + appDetail, err = fetchResourceTreeForACD(rctx, cn, appDetail, impl) } else if util.IsHelmApp(appDetail.DeploymentAppType) { config, err := impl.helmAppService.GetClusterConf(appDetail.ClusterId) if err != nil { @@ -1000,7 +962,7 @@ func (impl InstalledAppServiceImpl) FetchResourceTree(rctx context.Context, cn h appDetail.ResourceTree = map[string]interface{}{} } } - return *appDetail, nil + return *appDetail, err } func (impl InstalledAppServiceImpl) MarkGitOpsInstalledAppsDeletedIfArgoAppIsDeleted(installedAppId int, envId int) error { @@ -1060,3 +1022,126 @@ func (impl InstalledAppServiceImpl) CheckAppExistsByInstalledAppId(installedAppI _, err := impl.installedAppRepository.GetInstalledApp(installedAppId) return err } + +func (impl InstalledAppServiceImpl) FetchResourceTreeWithHibernateForACD(rctx context.Context, cn http.CloseNotifier, appDetail *bean2.AppDetailContainer) bean2.AppDetailContainer { + ctx, cancel := context.WithCancel(rctx) + if cn != nil { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + acdToken, err := impl.argoUserService.GetLatestDevtronArgoCdUserToken() + if err != nil { + impl.logger.Errorw("error in getting acd token", "err", err) + return *appDetail + } + ctx = context.WithValue(ctx, "token", acdToken) + defer cancel() + appDetail, err = fetchResourceTreeForACD(rctx, cn, appDetail, impl) + appDetail.ResourceTree = checkHibernate(impl, appDetail, ctx) + return *appDetail +} +func checkHibernate(impl InstalledAppServiceImpl, resp *bean2.AppDetailContainer, ctx context.Context) map[string]interface{} { + + responseTree := resp.ResourceTree + + for _, node := range responseTree["nodes"].(interface{}).([]interface{}) { + currNode := node.(interface{}).(map[string]interface{}) + name := resp.AppName + "-" + resp.Namespace + resName := util3.InterfaceToString(currNode["name"]) + resKind := util3.InterfaceToString(currNode["kind"]) + resGroup := util3.InterfaceToString(currNode["group"]) + resVersion := util3.InterfaceToString(currNode["version"]) + resNamespace := util3.InterfaceToString(currNode["namespace"]) + rQuery := &application.ApplicationResourceRequest{ + Name: &name, + ResourceName: &resName, + Kind: &resKind, + Group: &resGroup, + Version: &resVersion, + Namespace: &resNamespace, + } + ctx, _ := context.WithTimeout(ctx, 60*time.Second) + if currNode["parentRefs"] == nil { + + res, err := impl.acdClient.GetResource(ctx, rQuery) + if err != nil { + impl.logger.Errorw("GRPC_GET_RESOURCE", "data", res, "timeTaken", time.Since(time.Now()), "err", err) + return responseTree + } + if res.Manifest != nil { + manifest, _ := gjson.Parse(*res.Manifest).Value().(map[string]interface{}) + replicas := util3.InterfaceToMapAdapter(manifest["spec"])["replicas"] + if replicas != nil { + currNode["canBeHibernated"] = true + } + annotations := util3.InterfaceToMapAdapter(manifest["metadata"])["annotations"] + if annotations != nil { + val := util3.InterfaceToMapAdapter(annotations)["hibernator.devtron.ai/replicas"] + if val != nil { + if util3.InterfaceToString(val) != "0" && util3.InterfaceToFloat(replicas) == 0 { + currNode["isHibernated"] = true + } + } + } + + } + + if err != nil { + impl.logger.Errorw("GRPC_GET_RESOURCE", "data", res, "timeTaken", time.Since(time.Now()), "err", err) + } + } + node = currNode + } + return responseTree +} + +func fetchResourceTreeForACD(rctx context.Context, cn http.CloseNotifier, appDetail *bean2.AppDetailContainer, impl InstalledAppServiceImpl) (*bean2.AppDetailContainer, error) { + acdAppName := appDetail.AppName + "-" + appDetail.EnvironmentName + query := &application.ResourcesQuery{ + ApplicationName: &acdAppName, + } + ctx, cancel := context.WithCancel(rctx) + if cn != nil { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + acdToken, err := impl.argoUserService.GetLatestDevtronArgoCdUserToken() + if err != nil { + impl.logger.Errorw("error in getting acd token", "err", err) + return appDetail, err + } + ctx = context.WithValue(ctx, "token", acdToken) + defer cancel() + start := time.Now() + resp, err := impl.acdClient.ResourceTree(ctx, query) + elapsed := time.Since(start) + impl.logger.Debugf("Time elapsed %s in fetching app-store installed application %s for environment %s", elapsed, appDetail.InstalledAppId, appDetail.EnvironmentId) + if err != nil { + impl.logger.Errorw("service err, FetchAppDetailsForInstalledApp, fetching resource tree", "err", err, "installedAppId", appDetail.InstalledAppId, "envId", appDetail.EnvironmentId) + err = &util.ApiError{ + Code: constants.AppDetailResourceTreeNotFound, + InternalMessage: "app detail fetched, failed to get resource tree from acd", + UserMessage: "app detail fetched, failed to get resource tree from acd", + } + appDetail.ResourceTree = map[string]interface{}{} + return appDetail, err + } + // TODO: using this resp.Status to update in app_status table + appDetail.ResourceTree = util3.InterfaceToMapAdapter(resp) + err = impl.appStatusService.UpdateStatusWithAppIdEnvId(appDetail.AppId, appDetail.EnvironmentId, resp.Status) + if err != nil { + impl.logger.Warnw("error in updating app status", "err", err, appDetail.AppId, "envId", appDetail.EnvironmentId) + } + impl.logger.Debugf("application %s in environment %s had status %+v\n", appDetail.InstalledAppId, appDetail.EnvironmentId, resp) + return appDetail, err +} diff --git a/pkg/chartRepo/ChartRepositoryService_test.go b/pkg/chartRepo/ChartRepositoryService_test.go index 221b25823b..3ddd406966 100644 --- a/pkg/chartRepo/ChartRepositoryService_test.go +++ b/pkg/chartRepo/ChartRepositoryService_test.go @@ -2,12 +2,12 @@ package chartRepo import ( "encoding/json" - "testing" "github.com/devtron-labs/devtron/internal/sql/repository" "github.com/devtron-labs/devtron/internal/util" "github.com/ghodss/yaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "testing" ) type ChartRepositoryServiceMock struct { @@ -108,7 +108,7 @@ func TestUpdateRepository_NewRepository(t *testing.T) { var newRepositories []*AcdConfigMapRepositoriesDto repoByte, _ := yaml.YAMLToJSON([]byte(newYaml)) json.Unmarshal(repoByte, &newRepositories) - + // verify assert.Equal(t, "https://github.com/devtron/myrepo2/", newRepositories[1].Url) assert.Equal(t, "myuser", newRepositories[1].UsernameSecret.Name) diff --git a/pkg/clusterTerminalAccess/UserTerminalAccessService.go b/pkg/clusterTerminalAccess/UserTerminalAccessService.go index a61ccda963..03febb2103 100644 --- a/pkg/clusterTerminalAccess/UserTerminalAccessService.go +++ b/pkg/clusterTerminalAccess/UserTerminalAccessService.go @@ -532,7 +532,7 @@ func replaceTemplateData(templateData string, podNameVar string, namespace strin return templateData, nil } -//template data use kubernetes object +// template data use kubernetes object func (impl *UserTerminalAccessServiceImpl) applyTemplateData(ctx context.Context, request *models.UserTerminalSessionRequest, podNameVar string, terminalTemplate *models.TerminalAccessTemplates, isUpdate bool, isAutoSelect bool) error { templateName := terminalTemplate.TemplateName diff --git a/pkg/pipeline/PipelineStageService.go b/pkg/pipeline/PipelineStageService.go index 520cf92f60..bb34cd720f 100644 --- a/pkg/pipeline/PipelineStageService.go +++ b/pkg/pipeline/PipelineStageService.go @@ -240,7 +240,7 @@ func (impl *PipelineStageServiceImpl) BuildVariableAndConditionDataByStepIdDeepC return inputVariablesDto, outputVariablesDto, conditionsDto, nil } -//GetCiPipelineStageData and related methods starts +// GetCiPipelineStageData and related methods starts func (impl *PipelineStageServiceImpl) GetCiPipelineStageData(ciPipelineId int) (*bean.PipelineStageDto, *bean.PipelineStageDto, error) { //getting all stages by ci pipeline id @@ -452,7 +452,7 @@ func (impl *PipelineStageServiceImpl) BuildVariableAndConditionDataByStepId(step //GetCiPipelineStageData and related methods ends -//CreateCiStage and related methods starts +// CreateCiStage and related methods starts func (impl *PipelineStageServiceImpl) CreateCiStage(stageReq *bean.PipelineStageDto, stageType repository.PipelineStageType, ciPipelineId int, userId int32) error { stage := &repository.PipelineStage{ Name: stageReq.Name, @@ -805,7 +805,7 @@ func (impl *PipelineStageServiceImpl) CreateConditionsEntryInDb(stepId int, cond //CreateCiStage and related methods ends -//UpdateCiStage and related methods starts +// UpdateCiStage and related methods starts func (impl *PipelineStageServiceImpl) UpdateCiStage(stageReq *bean.PipelineStageDto, stageType repository.PipelineStageType, ciPipelineId int, userId int32) error { //getting stage by stageType and ciPipelineId stageOld, err := impl.pipelineStageRepository.GetCiStageByCiPipelineIdAndStageType(ciPipelineId, stageType) @@ -1341,7 +1341,7 @@ func (impl *PipelineStageServiceImpl) UpdatePipelineStageStepConditions(stepId i //UpdateCiStage and related methods ends -//DeleteCiStage and related methods starts +// DeleteCiStage and related methods starts func (impl *PipelineStageServiceImpl) DeleteCiStage(stageReq *bean.PipelineStageDto, userId int32, tx *pg.Tx) error { //marking stage deleted err := impl.pipelineStageRepository.MarkCiStageDeletedById(stageReq.Id, userId, tx) @@ -1416,7 +1416,7 @@ func (impl *PipelineStageServiceImpl) DeleteCiStage(stageReq *bean.PipelineStage //DeleteCiStage and related methods starts -//BuildPrePostAndRefPluginStepsDataForWfRequest and related methods starts +// BuildPrePostAndRefPluginStepsDataForWfRequest and related methods starts func (impl *PipelineStageServiceImpl) BuildPrePostAndRefPluginStepsDataForWfRequest(ciPipelineId int) ([]*bean.StepObject, []*bean.StepObject, []*bean.RefPluginObject, error) { //get all stages By ciPipelineId ciStages, err := impl.pipelineStageRepository.GetAllCiStagesByCiPipelineId(ciPipelineId) diff --git a/util/k8s/k8sCapacityService.go b/util/k8s/k8sCapacityService.go index 5659c4e471..bace795ee9 100644 --- a/util/k8s/k8sCapacityService.go +++ b/util/k8s/k8sCapacityService.go @@ -53,7 +53,7 @@ const ( KopsNodeGroupLabel = "kops.k8s.io/instancegroup" ) -//TODO: add any new nodeGrouplabel in this array +// TODO: add any new nodeGrouplabel in this array var NodeGroupLabels = [4]string{AWSNodeGroupLabel, AzureNodeGroupLabel, GcpNodeGroupLabel, KopsNodeGroupLabel} // below const set is used for pod delete status From 741698f9e0e38916d811a932ae1edbbe0725628e Mon Sep 17 00:00:00 2001 From: Ashish-devtron Date: Thu, 23 Mar 2023 16:18:07 +0530 Subject: [PATCH 7/7] add-check-for-nil-nodes --- pkg/appStore/deployment/service/InstalledAppService.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/appStore/deployment/service/InstalledAppService.go b/pkg/appStore/deployment/service/InstalledAppService.go index 3f11f4e147..24f58a7604 100644 --- a/pkg/appStore/deployment/service/InstalledAppService.go +++ b/pkg/appStore/deployment/service/InstalledAppService.go @@ -1051,6 +1051,12 @@ func (impl InstalledAppServiceImpl) FetchResourceTreeWithHibernateForACD(rctx co ctx = context.WithValue(ctx, "token", acdToken) defer cancel() appDetail, err = fetchResourceTreeForACD(rctx, cn, appDetail, impl) + if err != nil { + return *appDetail + } + if appDetail.ResourceTree["nodes"] == nil { + return *appDetail + } appDetail.ResourceTree = checkHibernate(impl, appDetail, ctx) return *appDetail }