diff --git a/Wire.go b/Wire.go index ddd2736895..5e35c118ad 100644 --- a/Wire.go +++ b/Wire.go @@ -819,6 +819,9 @@ func InitializeApp() (*App, error) { kubernetesResourceAuditLogs.Newk8sResourceHistoryServiceImpl, wire.Bind(new(kubernetesResourceAuditLogs.K8sResourceHistoryService), new(*kubernetesResourceAuditLogs.K8sResourceHistoryServiceImpl)), + + router.NewAppGroupingRouterImpl, + wire.Bind(new(router.AppGroupingRouter), new(*router.AppGroupingRouterImpl)), ) return &App{}, nil } diff --git a/api/restHandler/AppListingRestHandler.go b/api/restHandler/AppListingRestHandler.go index 7e9098d3a1..2ccfd26230 100644 --- a/api/restHandler/AppListingRestHandler.go +++ b/api/restHandler/AppListingRestHandler.go @@ -305,12 +305,13 @@ func (handler AppListingRestHandlerImpl) FetchAppsByEnvironment(w http.ResponseW offset := fetchAppListingRequest.Offset limit := fetchAppListingRequest.Size - if offset+limit <= len(apps) { - apps = apps[offset : offset+limit] - } else { - apps = apps[offset:] + if limit > 0 { + if offset+limit <= len(apps) { + apps = apps[offset : offset+limit] + } else { + apps = apps[offset:] + } } - appContainerResponse := bean.AppContainerResponse{ AppContainers: apps, AppCount: appsCount, diff --git a/api/restHandler/AppWorkflowRestHandler.go b/api/restHandler/AppWorkflowRestHandler.go index 56227871bb..ae5fbf8302 100644 --- a/api/restHandler/AppWorkflowRestHandler.go +++ b/api/restHandler/AppWorkflowRestHandler.go @@ -32,6 +32,7 @@ import ( "go.uber.org/zap" "net/http" "strconv" + "strings" ) type AppWorkflowRestHandler interface { @@ -39,6 +40,7 @@ type AppWorkflowRestHandler interface { FindAppWorkflow(w http.ResponseWriter, r *http.Request) DeleteAppWorkflow(w http.ResponseWriter, r *http.Request) FindAllWorkflows(w http.ResponseWriter, r *http.Request) + FindAppWorkflowByEnvironment(w http.ResponseWriter, r *http.Request) } type AppWorkflowRestHandlerImpl struct { @@ -216,3 +218,50 @@ func (impl AppWorkflowRestHandlerImpl) FindAllWorkflows(w http.ResponseWriter, r } common.WriteJsonResp(w, nil, resp, http.StatusOK) } + +func (impl AppWorkflowRestHandlerImpl) FindAppWorkflowByEnvironment(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + userId, err := impl.userAuthService.GetLoggedInUser(r) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + user, err := impl.userAuthService.GetById(userId) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + userEmailId := strings.ToLower(user.EmailId) + envId, err := strconv.Atoi(vars["envId"]) + if err != nil { + impl.Logger.Errorw("bad request", "err", err) + common.WriteJsonResp(w, err, nil, http.StatusBadRequest) + return + } + workflows := make(map[string]interface{}) + workflowsList, err := impl.appWorkflowService.FindAppWorkflowsByEnvironmentId(envId, userEmailId, impl.checkAuthBatch) + if err != nil { + impl.Logger.Errorw("error in fetching workflows for app", "err", err) + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + return + } + workflows["envId"] = envId + if len(workflowsList) > 0 { + workflows["workflows"] = workflowsList + } else { + workflows["workflows"] = []appWorkflow.AppWorkflowDto{} + } + common.WriteJsonResp(w, err, workflows, http.StatusOK) +} + +func (handler *AppWorkflowRestHandlerImpl) checkAuthBatch(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool) { + var appResult map[string]bool + var envResult map[string]bool + if len(appObject) > 0 { + appResult = handler.enforcer.EnforceByEmailInBatch(emailId, casbin.ResourceApplications, casbin.ActionGet, appObject) + } + if len(envObject) > 0 { + envResult = handler.enforcer.EnforceByEmailInBatch(emailId, casbin.ResourceEnvironment, casbin.ActionGet, envObject) + } + return appResult, envResult +} diff --git a/api/restHandler/app/BuildPipelineRestHandler.go b/api/restHandler/app/BuildPipelineRestHandler.go index 887fd2353f..834702d7bd 100644 --- a/api/restHandler/app/BuildPipelineRestHandler.go +++ b/api/restHandler/app/BuildPipelineRestHandler.go @@ -40,6 +40,8 @@ type DevtronAppBuildRestHandler interface { CancelWorkflow(w http.ResponseWriter, r *http.Request) UpdateBranchCiPipelinesWithRegex(w http.ResponseWriter, r *http.Request) + GetCiPipelineByEnvironment(w http.ResponseWriter, r *http.Request) + GetExternalCiByEnvironment(w http.ResponseWriter, r *http.Request) } type DevtronAppBuildMaterialRestHandler interface { @@ -1243,3 +1245,58 @@ func (handler PipelineConfigRestHandlerImpl) FetchWorkflowDetails(w http.Respons } common.WriteJsonResp(w, err, resp, http.StatusOK) } + +func (handler PipelineConfigRestHandlerImpl) GetCiPipelineByEnvironment(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + userId, err := handler.userAuthService.GetLoggedInUser(r) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + user, err := handler.userAuthService.GetById(userId) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + userEmailId := strings.ToLower(user.EmailId) + envId, err := strconv.Atoi(vars["envId"]) + if err != nil { + handler.Logger.Errorw("request err, GetCdPipelines", "err", err, "envId", envId) + common.WriteJsonResp(w, err, nil, http.StatusBadRequest) + return + } + ciConf, err := handler.pipelineBuilder.GetCiPipelineByEnvironment(envId, userEmailId, handler.checkAuthBatch) + if err != nil { + handler.Logger.Errorw("service err, GetCiPipeline", "err", err, "envId", envId) + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + return + } + common.WriteJsonResp(w, err, ciConf, http.StatusOK) +} + +func (handler PipelineConfigRestHandlerImpl) GetExternalCiByEnvironment(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + userId, err := handler.userAuthService.GetLoggedInUser(r) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + user, err := handler.userAuthService.GetById(userId) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + userEmailId := strings.ToLower(user.EmailId) + envId, err := strconv.Atoi(vars["envId"]) + if err != nil { + common.WriteJsonResp(w, err, nil, http.StatusBadRequest) + return + } + ciConf, err := handler.pipelineBuilder.GetExternalCiByEnvironment(envId, userEmailId, handler.checkAuthBatch) + if err != nil { + handler.Logger.Errorw("service err, GetExternalCi", "err", err, "envId", envId) + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + return + } + common.WriteJsonResp(w, err, ciConf, http.StatusOK) +} diff --git a/api/restHandler/app/DeploymentPipelineRestHandler.go b/api/restHandler/app/DeploymentPipelineRestHandler.go index 7a50fab46b..3d508af28f 100644 --- a/api/restHandler/app/DeploymentPipelineRestHandler.go +++ b/api/restHandler/app/DeploymentPipelineRestHandler.go @@ -35,6 +35,7 @@ type DevtronAppDeploymentRestHandler interface { IsReadyToTrigger(w http.ResponseWriter, r *http.Request) FetchCdWorkflowDetails(w http.ResponseWriter, r *http.Request) + GetCdPipelinesByEnvironment(w http.ResponseWriter, r *http.Request) } type DevtronAppDeploymentConfigRestHandler interface { @@ -1578,13 +1579,7 @@ func (handler PipelineConfigRestHandlerImpl) GetCdPipelineById(w http.ResponseWr return } - envId, err := handler.pipelineBuilder.GetEnvironmentByCdPipelineId(pipelineId) - if err != nil { - common.WriteJsonResp(w, err, nil, http.StatusBadRequest) - return - } - - envObject := handler.enforcerUtil.GetEnvRBACNameByCdPipelineIdAndEnvId(pipelineId, envId) + envObject := handler.enforcerUtil.GetEnvRBACNameByCdPipelineIdAndEnvId(pipelineId) if ok := handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionUpdate, envObject); !ok { common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), "Unauthorized User", http.StatusForbidden) return @@ -1874,3 +1869,43 @@ func (handler PipelineConfigRestHandlerImpl) UpgradeForAllApps(w http.ResponseWr response["failed"] = failedIds common.WriteJsonResp(w, err, response, http.StatusOK) } + +func (handler PipelineConfigRestHandlerImpl) GetCdPipelinesByEnvironment(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + userId, err := handler.userAuthService.GetLoggedInUser(r) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + user, err := handler.userAuthService.GetById(userId) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + userEmailId := strings.ToLower(user.EmailId) + envId, err := strconv.Atoi(vars["envId"]) + if err != nil { + handler.Logger.Errorw("request err, GetCdPipelines", "err", err, "envId", envId) + common.WriteJsonResp(w, err, nil, http.StatusBadRequest) + return + } + results, err := handler.pipelineBuilder.GetCdPipelinesByEnvironment(envId, userEmailId, handler.checkAuthBatch) + if err != nil { + handler.Logger.Errorw("service err, GetCdPipelines", "err", err, "envId", envId) + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + return + } + common.WriteJsonResp(w, err, results, http.StatusOK) +} + +func (handler *PipelineConfigRestHandlerImpl) checkAuthBatch(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool) { + var appResult map[string]bool + var envResult map[string]bool + if len(appObject) > 0 { + appResult = handler.enforcer.EnforceByEmailInBatch(emailId, casbin.ResourceApplications, casbin.ActionGet, appObject) + } + if len(envObject) > 0 { + envResult = handler.enforcer.EnforceByEmailInBatch(emailId, casbin.ResourceEnvironment, casbin.ActionGet, envObject) + } + return appResult, envResult +} diff --git a/api/restHandler/app/PipelineConfigRestHandler.go b/api/restHandler/app/PipelineConfigRestHandler.go index 436a4f5c0f..d527e79041 100644 --- a/api/restHandler/app/PipelineConfigRestHandler.go +++ b/api/restHandler/app/PipelineConfigRestHandler.go @@ -64,10 +64,14 @@ type DevtronAppRestHandler interface { FindAppsByTeamId(w http.ResponseWriter, r *http.Request) FindAppsByTeamName(w http.ResponseWriter, r *http.Request) + GetEnvironmentListWithAppData(w http.ResponseWriter, r *http.Request) + GetApplicationsByEnvironment(w http.ResponseWriter, r *http.Request) } type DevtronAppWorkflowRestHandler interface { FetchAppWorkflowStatusForTriggerView(w http.ResponseWriter, r *http.Request) + FetchAppWorkflowStatusForTriggerViewByEnvironment(w http.ResponseWriter, r *http.Request) + FetchAppDeploymentStatusForEnvironments(w http.ResponseWriter, r *http.Request) } type PipelineConfigRestHandler interface { @@ -546,3 +550,156 @@ func (handler PipelineConfigRestHandlerImpl) PipelineNameSuggestion(w http.Respo } common.WriteJsonResp(w, err, suggestedName, http.StatusOK) } + +func (handler PipelineConfigRestHandlerImpl) FetchAppWorkflowStatusForTriggerViewByEnvironment(w http.ResponseWriter, r *http.Request) { + userId, err := handler.userAuthService.GetLoggedInUser(r) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + user, err := handler.userAuthService.GetById(userId) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + userEmailId := strings.ToLower(user.EmailId) + vars := mux.Vars(r) + envId, err := strconv.Atoi(vars["envId"]) + if err != nil { + common.WriteJsonResp(w, err, nil, http.StatusBadRequest) + return + } + triggerWorkflowStatus := pipelineConfig.TriggerWorkflowStatus{} + ciWorkflowStatus, err := handler.ciHandler.FetchCiStatusForTriggerViewForEnvironment(envId, userEmailId, handler.checkAuthBatch) + if err != nil { + handler.Logger.Errorw("service err", "err", err) + if util.IsErrNoRows(err) { + err = &util.ApiError{Code: "404", HttpStatusCode: 200, UserMessage: "no workflow found"} + common.WriteJsonResp(w, err, nil, http.StatusOK) + } else { + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + } + return + } + + cdWorkflowStatus, err := handler.cdHandler.FetchAppWorkflowStatusForTriggerViewForEnvironment(envId, userEmailId, handler.checkAuthBatch) + if err != nil { + handler.Logger.Errorw("service err, FetchAppWorkflowStatusForTriggerView", "err", err) + if util.IsErrNoRows(err) { + err = &util.ApiError{Code: "404", HttpStatusCode: 200, UserMessage: "no status found"} + common.WriteJsonResp(w, err, nil, http.StatusOK) + } else { + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + } + return + } + triggerWorkflowStatus.CiWorkflowStatus = ciWorkflowStatus + triggerWorkflowStatus.CdWorkflowStatus = cdWorkflowStatus + common.WriteJsonResp(w, err, triggerWorkflowStatus, http.StatusOK) +} + +func (handler PipelineConfigRestHandlerImpl) GetEnvironmentListWithAppData(w http.ResponseWriter, r *http.Request) { + v := r.URL.Query() + userId, err := handler.userAuthService.GetLoggedInUser(r) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + user, err := handler.userAuthService.GetById(userId) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + userEmailId := strings.ToLower(user.EmailId) + envName := v.Get("envName") + clusterIdString := v.Get("clusterIds") + offset := 0 + offsetStr := v.Get("offset") + if len(offsetStr) > 0 { + offset, _ = strconv.Atoi(offsetStr) + } + size := 0 + sizeStr := v.Get("size") + if len(sizeStr) > 0 { + size, _ = strconv.Atoi(sizeStr) + } + var clusterIds []int + if clusterIdString != "" { + clusterIdSlices := strings.Split(clusterIdString, ",") + for _, clusterId := range clusterIdSlices { + id, err := strconv.Atoi(clusterId) + if err != nil { + common.WriteJsonResp(w, err, "please send valid cluster Ids", http.StatusBadRequest) + return + } + clusterIds = append(clusterIds, id) + } + } + result, err := handler.pipelineBuilder.GetEnvironmentListForAutocompleteFilter(envName, clusterIds, offset, size, userEmailId, handler.checkAuthBatch) + if err != nil { + handler.Logger.Errorw("service err, get app", "err", err) + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + return + } + common.WriteJsonResp(w, err, result, http.StatusOK) +} + +func (handler PipelineConfigRestHandlerImpl) GetApplicationsByEnvironment(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + userId, err := handler.userAuthService.GetLoggedInUser(r) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + user, err := handler.userAuthService.GetById(userId) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + userEmailId := strings.ToLower(user.EmailId) + envId, err := strconv.Atoi(vars["envId"]) + if err != nil { + handler.Logger.Errorw("request err, get app", "err", err, "envId", envId) + common.WriteJsonResp(w, err, nil, http.StatusBadRequest) + return + } + ciConf, err := handler.pipelineBuilder.GetAppListForEnvironment(envId, userEmailId, handler.checkAuthBatch) + if err != nil { + handler.Logger.Errorw("service err, get app", "err", err) + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + return + } + common.WriteJsonResp(w, err, ciConf, http.StatusOK) +} + +func (handler PipelineConfigRestHandlerImpl) FetchAppDeploymentStatusForEnvironments(w http.ResponseWriter, r *http.Request) { + userId, err := handler.userAuthService.GetLoggedInUser(r) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + user, err := handler.userAuthService.GetById(userId) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + userEmailId := strings.ToLower(user.EmailId) + vars := mux.Vars(r) + envId, err := strconv.Atoi(vars["envId"]) + if err != nil { + common.WriteJsonResp(w, err, nil, http.StatusBadRequest) + return + } + results, err := handler.cdHandler.FetchAppDeploymentStatusForEnvironments(envId, userEmailId, handler.checkAuthBatch) + if err != nil { + handler.Logger.Errorw("service err, FetchAppWorkflowStatusForTriggerView", "err", err) + if util.IsErrNoRows(err) { + err = &util.ApiError{Code: "404", HttpStatusCode: 200, UserMessage: "no status found"} + common.WriteJsonResp(w, err, nil, http.StatusOK) + } else { + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + } + return + } + common.WriteJsonResp(w, err, results, http.StatusOK) +} diff --git a/api/router/AppGroupingRouter.go b/api/router/AppGroupingRouter.go new file mode 100644 index 0000000000..c83f97e42e --- /dev/null +++ b/api/router/AppGroupingRouter.go @@ -0,0 +1,35 @@ +package router + +import ( + "github.com/devtron-labs/devtron/api/restHandler" + "github.com/devtron-labs/devtron/api/restHandler/app" + "github.com/gorilla/mux" +) + +type AppGroupingRouter interface { + InitAppGroupingRouter(router *mux.Router) +} +type AppGroupingRouterImpl struct { + restHandler app.PipelineConfigRestHandler + appWorkflowRestHandler restHandler.AppWorkflowRestHandler +} + +func NewAppGroupingRouterImpl(restHandler app.PipelineConfigRestHandler, appWorkflowRestHandler restHandler.AppWorkflowRestHandler) *AppGroupingRouterImpl { + return &AppGroupingRouterImpl{ + restHandler: restHandler, + appWorkflowRestHandler: appWorkflowRestHandler, + } +} + +func (router AppGroupingRouterImpl) InitAppGroupingRouter(appGroupingRouter *mux.Router) { + appGroupingRouter.Path("/{envId}/app-wf"). + HandlerFunc(router.appWorkflowRestHandler.FindAppWorkflowByEnvironment).Methods("GET") + appGroupingRouter.Path("/{envId}/ci-pipeline").HandlerFunc(router.restHandler.GetCiPipelineByEnvironment).Methods("GET") + appGroupingRouter.Path("/{envId}/cd-pipeline").HandlerFunc(router.restHandler.GetCdPipelinesByEnvironment).Methods("GET") + appGroupingRouter.Path("/{envId}/external-ci").HandlerFunc(router.restHandler.GetExternalCiByEnvironment).Methods("GET") + appGroupingRouter.Path("/{envId}/workflow/status").HandlerFunc(router.restHandler.FetchAppWorkflowStatusForTriggerViewByEnvironment).Methods("GET") + appGroupingRouter.Path("/app-grouping").HandlerFunc(router.restHandler.GetEnvironmentListWithAppData).Methods("GET") + appGroupingRouter.Path("/{envId}/applications").HandlerFunc(router.restHandler.GetApplicationsByEnvironment).Methods("GET") + appGroupingRouter.Path("/{envId}/deployment/status").HandlerFunc(router.restHandler.FetchAppDeploymentStatusForEnvironments).Methods("GET") + +} diff --git a/api/router/router.go b/api/router/router.go index 2300652679..b3915f86cb 100644 --- a/api/router/router.go +++ b/api/router/router.go @@ -116,6 +116,7 @@ type MuxRouter struct { globalCMCSRouter GlobalCMCSRouter userTerminalAccessRouter terminal2.UserTerminalAccessRouter ciStatusUpdateCron cron.CiStatusUpdateCron + appGroupingRouter AppGroupingRouter } func NewMuxRouter(logger *zap.SugaredLogger, HelmRouter PipelineTriggerRouter, PipelineConfigRouter PipelineConfigRouter, @@ -143,7 +144,8 @@ func NewMuxRouter(logger *zap.SugaredLogger, HelmRouter PipelineTriggerRouter, P serverRouter server.ServerRouter, apiTokenRouter apiToken.ApiTokenRouter, helmApplicationStatusUpdateHandler cron.CdApplicationStatusUpdateHandler, k8sCapacityRouter k8s.K8sCapacityRouter, webhookHelmRouter webhookHelm.WebhookHelmRouter, globalCMCSRouter GlobalCMCSRouter, - userTerminalAccessRouter terminal2.UserTerminalAccessRouter, ciStatusUpdateCron cron.CiStatusUpdateCron) *MuxRouter { + userTerminalAccessRouter terminal2.UserTerminalAccessRouter, ciStatusUpdateCron cron.CiStatusUpdateCron, + appGroupingRouter AppGroupingRouter) *MuxRouter { r := &MuxRouter{ Router: mux.NewRouter(), HelmRouter: HelmRouter, @@ -210,6 +212,7 @@ func NewMuxRouter(logger *zap.SugaredLogger, HelmRouter PipelineTriggerRouter, P globalCMCSRouter: globalCMCSRouter, userTerminalAccessRouter: userTerminalAccessRouter, ciStatusUpdateCron: ciStatusUpdateCron, + appGroupingRouter: appGroupingRouter, } return r } @@ -263,6 +266,7 @@ func (r MuxRouter) Init() { environmentClusterMappingsRouter := r.Router.PathPrefix("/orchestrator/env").Subrouter() r.EnvironmentClusterMappingsRouter.InitEnvironmentClusterMappingsRouter(environmentClusterMappingsRouter) + r.appGroupingRouter.InitAppGroupingRouter(environmentClusterMappingsRouter) clusterRouter := r.Router.PathPrefix("/orchestrator/cluster").Subrouter() r.ClusterRouter.InitClusterRouter(clusterRouter) diff --git a/cmd/external-app/wire_gen.go b/cmd/external-app/wire_gen.go index 28838b9eb9..e1f11f4195 100644 --- a/cmd/external-app/wire_gen.go +++ b/cmd/external-app/wire_gen.go @@ -1,6 +1,6 @@ // Code generated by Wire. DO NOT EDIT. -//go:generate go run github.com/google/wire/cmd/wire +//go:generate wire //+build !wireinject package main @@ -34,6 +34,7 @@ import ( "github.com/devtron-labs/devtron/client/telemetry" repository4 "github.com/devtron-labs/devtron/internal/sql/repository" "github.com/devtron-labs/devtron/internal/sql/repository/app" + "github.com/devtron-labs/devtron/internal/sql/repository/appStatus" "github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig" "github.com/devtron-labs/devtron/internal/util" "github.com/devtron-labs/devtron/pkg/apiToken" @@ -149,7 +150,8 @@ func InitializeApp() (*App, error) { v := informer.NewGlobalMapClusterNamespace() k8sInformerFactoryImpl := informer.NewK8sInformerFactoryImpl(sugaredLogger, v, runtimeConfig) clusterServiceImpl := cluster.NewClusterServiceImpl(clusterRepositoryImpl, sugaredLogger, k8sUtil, k8sInformerFactoryImpl, userAuthRepositoryImpl, userRepositoryImpl, roleGroupRepositoryImpl) - environmentRepositoryImpl := repository2.NewEnvironmentRepositoryImpl(db) + appStatusRepositoryImpl := appStatus.NewAppStatusRepositoryImpl(db, sugaredLogger) + environmentRepositoryImpl := repository2.NewEnvironmentRepositoryImpl(db, sugaredLogger, appStatusRepositoryImpl) environmentServiceImpl := cluster.NewEnvironmentServiceImpl(environmentRepositoryImpl, clusterServiceImpl, sugaredLogger, k8sUtil, k8sInformerFactoryImpl, userAuthServiceImpl) chartRepoRepositoryImpl := chartRepoRepository.NewChartRepoRepositoryImpl(db) acdAuthConfig, err := util3.GetACDAuthConfig() @@ -193,7 +195,7 @@ func InitializeApp() (*App, error) { serverDataStoreServerDataStore := serverDataStore.InitServerDataStore() appStoreApplicationVersionRepositoryImpl := appStoreDiscoverRepository.NewAppStoreApplicationVersionRepositoryImpl(sugaredLogger, db) pipelineRepositoryImpl := pipelineConfig.NewPipelineRepositoryImpl(db, sugaredLogger) - helmAppServiceImpl := client2.NewHelmAppServiceImpl(sugaredLogger, clusterServiceImpl, helmAppClientImpl, pumpImpl, enforcerUtilHelmImpl, serverDataStoreServerDataStore, serverEnvConfigServerEnvConfig, appStoreApplicationVersionRepositoryImpl, environmentServiceImpl, pipelineRepositoryImpl, installedAppRepositoryImpl, appRepositoryImpl) + helmAppServiceImpl := client2.NewHelmAppServiceImpl(sugaredLogger, clusterServiceImpl, helmAppClientImpl, pumpImpl, enforcerUtilHelmImpl, serverDataStoreServerDataStore, serverEnvConfigServerEnvConfig, appStoreApplicationVersionRepositoryImpl, environmentServiceImpl, pipelineRepositoryImpl, installedAppRepositoryImpl, appRepositoryImpl, clusterRepositoryImpl) appStoreDeploymentCommonServiceImpl := appStoreDeploymentCommon.NewAppStoreDeploymentCommonServiceImpl(sugaredLogger, installedAppRepositoryImpl) attributesRepositoryImpl := repository4.NewAttributesRepositoryImpl(db) attributesServiceImpl := attributes.NewAttributesServiceImpl(sugaredLogger, attributesRepositoryImpl) diff --git a/internal/sql/repository/appWorkflow/AppWorkflowRepository.go b/internal/sql/repository/appWorkflow/AppWorkflowRepository.go index 724eacdd39..469212167d 100644 --- a/internal/sql/repository/appWorkflow/AppWorkflowRepository.go +++ b/internal/sql/repository/appWorkflow/AppWorkflowRepository.go @@ -32,6 +32,7 @@ type AppWorkflowRepository interface { FindById(id int) (*AppWorkflow, error) FindByIds(ids []int) (*AppWorkflow, error) FindByAppId(appId int) (appWorkflow []*AppWorkflow, err error) + FindByAppIds(appIds []int) (appWorkflow []*AppWorkflow, err error) DeleteAppWorkflow(appWorkflow *AppWorkflow, tx *pg.Tx) error SaveAppWorkflowMapping(wf *AppWorkflowMapping, tx *pg.Tx) (*AppWorkflowMapping, error) @@ -119,6 +120,14 @@ func (impl AppWorkflowRepositoryImpl) FindByAppId(appId int) (appWorkflow []*App return appWorkflow, err } +func (impl AppWorkflowRepositoryImpl) FindByAppIds(appIds []int) (appWorkflow []*AppWorkflow, err error) { + err = impl.dbConnection.Model(&appWorkflow). + Where("app_id in (?)", pg.In(appIds)). + Where("active = ?", true). + Select() + return appWorkflow, err +} + func (impl AppWorkflowRepositoryImpl) FindByIdAndAppId(id int, appId int) (*AppWorkflow, error) { appWorkflow := &AppWorkflow{} err := impl.dbConnection.Model(appWorkflow). diff --git a/internal/sql/repository/pipelineConfig/CdWorfkflowRepository.go b/internal/sql/repository/pipelineConfig/CdWorfkflowRepository.go index 5aa57f3aae..380647c4c0 100644 --- a/internal/sql/repository/pipelineConfig/CdWorfkflowRepository.go +++ b/internal/sql/repository/pipelineConfig/CdWorfkflowRepository.go @@ -207,6 +207,13 @@ type CiWorkflowStatus struct { StorageConfigured bool `json:"storageConfigured"` } +type AppDeploymentStatus struct { + AppId int `json:"appId"` + PipelineId int `json:"pipelineId"` + DeployStatus string `json:"deployStatus"` + WfrId int `json:"wfrId,omitempty"` +} + func NewCdWorkflowRepositoryImpl(dbConnection *pg.DB, logger *zap.SugaredLogger) *CdWorkflowRepositoryImpl { return &CdWorkflowRepositoryImpl{ dbConnection: dbConnection, diff --git a/internal/sql/repository/pipelineConfig/CiPipelineRepository.go b/internal/sql/repository/pipelineConfig/CiPipelineRepository.go index 6c51e7bcaf..656e768578 100644 --- a/internal/sql/repository/pipelineConfig/CiPipelineRepository.go +++ b/internal/sql/repository/pipelineConfig/CiPipelineRepository.go @@ -77,11 +77,13 @@ type CiPipelineRepository interface { FindExternalCiByCiPipelineId(ciPipelineId int) (*ExternalCiPipeline, error) FindExternalCiById(id int) (*ExternalCiPipeline, error) FindExternalCiByAppId(appId int) ([]*ExternalCiPipeline, error) + FindExternalCiByAppIds(appIds []int) ([]*ExternalCiPipeline, error) FindCiScriptsByCiPipelineId(ciPipelineId int) ([]*CiPipelineScript, error) SaveCiPipelineScript(ciPipelineScript *CiPipelineScript, tx *pg.Tx) error UpdateCiPipelineScript(script *CiPipelineScript, tx *pg.Tx) error MarkCiPipelineScriptsInactiveByCiPipelineId(ciPipelineId int, tx *pg.Tx) error FindByAppId(appId int) (pipelines []*CiPipeline, err error) + FindByAppIds(appIds []int) (pipelines []*CiPipeline, err error) //find non deleted pipeline FindById(id int) (pipeline *CiPipeline, err error) FindByCiAndAppDetailsById(pipelineId int) (pipeline *CiPipeline, err error) @@ -172,6 +174,15 @@ func (impl CiPipelineRepositoryImpl) FindByAppId(appId int) (pipelines []*CiPipe return pipelines, err } +func (impl CiPipelineRepositoryImpl) FindByAppIds(appIds []int) (pipelines []*CiPipeline, err error) { + err = impl.dbConnection.Model(&pipelines). + Column("ci_pipeline.*","App", "CiPipelineMaterials", "CiPipelineMaterials.GitMaterial"). + Where("ci_pipeline.app_id in (?)", pg.In(appIds)). + Where("deleted =? ", false). + Select() + return pipelines, err +} + func (impl CiPipelineRepositoryImpl) FindExternalCiByCiPipelineId(ciPipelineId int) (*ExternalCiPipeline, error) { externalCiPipeline := &ExternalCiPipeline{} err := impl.dbConnection.Model(externalCiPipeline). @@ -202,6 +213,16 @@ func (impl CiPipelineRepositoryImpl) FindExternalCiByAppId(appId int) ([]*Extern return externalCiPipeline, err } +func (impl CiPipelineRepositoryImpl) FindExternalCiByAppIds(appIds []int) ([]*ExternalCiPipeline, error) { + var externalCiPipeline []*ExternalCiPipeline + err := impl.dbConnection.Model(&externalCiPipeline). + Column("external_ci_pipeline.*"). + Where("app_id in(?)", pg.In(appIds)). + Where("active =? ", true). + Select() + return externalCiPipeline, err +} + func (impl CiPipelineRepositoryImpl) FindCiScriptsByCiPipelineId(ciPipelineId int) ([]*CiPipelineScript, error) { var ciPipelineScripts []*CiPipelineScript err := impl.dbConnection.Model(&ciPipelineScripts). diff --git a/internal/sql/repository/pipelineConfig/PipelineRepository.go b/internal/sql/repository/pipelineConfig/PipelineRepository.go index a2ca6c93eb..879ad553e6 100644 --- a/internal/sql/repository/pipelineConfig/PipelineRepository.go +++ b/internal/sql/repository/pipelineConfig/PipelineRepository.go @@ -98,6 +98,7 @@ type PipelineRepository interface { FindIdsByProjectIdsAndEnvironmentIds(projectIds, environmentIds []int) ([]int, error) GetArgoPipelineByArgoAppName(argoAppName string) (Pipeline, error) + FindActiveByAppIds(appIds []int) (pipelines []*Pipeline, err error) } type CiArtifactDTO struct { @@ -516,3 +517,12 @@ func (impl PipelineRepositoryImpl) GetArgoPipelineByArgoAppName(argoAppName stri } return pipeline, nil } + +func (impl PipelineRepositoryImpl) FindActiveByAppIds(appIds []int) (pipelines []*Pipeline, err error) { + err = impl.dbConnection.Model(&pipelines). + Column("pipeline.*", "App", "Environment"). + Where("app_id in(?)", pg.In(appIds)). + Where("deleted = ?", false). + Select() + return pipelines, err +} diff --git a/internal/sql/repository/pipelineConfig/mocks/PipelineRepository.go b/internal/sql/repository/pipelineConfig/mocks/PipelineRepository.go index c2ed8251d0..eadb38b89a 100644 --- a/internal/sql/repository/pipelineConfig/mocks/PipelineRepository.go +++ b/internal/sql/repository/pipelineConfig/mocks/PipelineRepository.go @@ -14,6 +14,11 @@ type PipelineRepository struct { mock.Mock } +func (_m *PipelineRepository) FindActiveByAppIds(appIds []int) (pipelines []*pipelineConfig.Pipeline, err error) { + // + return nil, err +} + // Delete provides a mock function with given fields: id, tx func (_m *PipelineRepository) Delete(id int, tx *pg.Tx) error { ret := _m.Called(id, tx) diff --git a/pkg/appWorkflow/AppWorkflowService.go b/pkg/appWorkflow/AppWorkflowService.go index 1efb1b0759..c0e5138857 100644 --- a/pkg/appWorkflow/AppWorkflowService.go +++ b/pkg/appWorkflow/AppWorkflowService.go @@ -24,6 +24,7 @@ import ( "github.com/devtron-labs/devtron/internal/util" "github.com/devtron-labs/devtron/pkg/pipeline" "github.com/devtron-labs/devtron/pkg/sql" + "github.com/devtron-labs/devtron/util/rbac" "github.com/go-pg/pg" "go.uber.org/zap" "time" @@ -47,6 +48,7 @@ type AppWorkflowService interface { FindAppWorkflowByName(name string, appId int) (AppWorkflowDto, error) FindAllWorkflowsComponentDetails(appId int) (*AllAppWorkflowComponentDetails, error) + FindAppWorkflowsByEnvironmentId(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]AppWorkflowDto, error) } type AppWorkflowServiceImpl struct { @@ -55,6 +57,7 @@ type AppWorkflowServiceImpl struct { ciCdPipelineOrchestrator pipeline.CiCdPipelineOrchestrator ciPipelineRepository pipelineConfig.CiPipelineRepository pipelineRepository pipelineConfig.PipelineRepository + enforcerUtil rbac.EnforcerUtil } type AppWorkflowDto struct { @@ -87,13 +90,16 @@ type WorkflowComponentNamesDto struct { CdPipelines []string `json:"cdPipelines"` } -func NewAppWorkflowServiceImpl(logger *zap.SugaredLogger, appWorkflowRepository appWorkflow.AppWorkflowRepository, ciCdPipelineOrchestrator pipeline.CiCdPipelineOrchestrator, ciPipelineRepository pipelineConfig.CiPipelineRepository, pipelineRepository pipelineConfig.PipelineRepository) *AppWorkflowServiceImpl { +func NewAppWorkflowServiceImpl(logger *zap.SugaredLogger, appWorkflowRepository appWorkflow.AppWorkflowRepository, + ciCdPipelineOrchestrator pipeline.CiCdPipelineOrchestrator, ciPipelineRepository pipelineConfig.CiPipelineRepository, + pipelineRepository pipelineConfig.PipelineRepository, enforcerUtil rbac.EnforcerUtil) *AppWorkflowServiceImpl { return &AppWorkflowServiceImpl{ Logger: logger, appWorkflowRepository: appWorkflowRepository, ciCdPipelineOrchestrator: ciCdPipelineOrchestrator, ciPipelineRepository: ciPipelineRepository, pipelineRepository: pipelineRepository, + enforcerUtil: enforcerUtil, } } @@ -380,3 +386,76 @@ func (impl AppWorkflowServiceImpl) FindAllWorkflowsComponentDetails(appId int) ( } return resp, nil } + +func (impl AppWorkflowServiceImpl) FindAppWorkflowsByEnvironmentId(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]AppWorkflowDto, error) { + workflows := make([]AppWorkflowDto, 0) + pipelines, err := impl.pipelineRepository.FindActiveByEnvId(envId) + if err != nil && err != pg.ErrNoRows { + impl.Logger.Errorw("error fetching pipelines for env id", "err", err) + return nil, err + } + pipelineMap := make(map[int]bool) + appNamesMap := make(map[int]string) + var appIds []int + //authorization block starts here + var envObjectArr []string + var appObjectArr []string + rbacObjectMap := make(map[int][]string) + for _, pipeline := range pipelines { + appObject := impl.enforcerUtil.GetAppRBACName(pipeline.App.AppName) + envObject := impl.enforcerUtil.GetEnvRBACNameByCdPipelineIdAndEnvId(pipeline.Id) + appObjectArr = append(appObjectArr, appObject) + envObjectArr = append(envObjectArr, envObject) + rbacObjectMap[pipeline.Id] = []string{appObject, envObject} + } + appResults, envResults := checkAuthBatch(emailId, appObjectArr, envObjectArr) + for _, pipeline := range pipelines { + appObject := rbacObjectMap[pipeline.Id][0] + envObject := rbacObjectMap[pipeline.Id][1] + if !(appResults[appObject] && envResults[envObject]) { + //if user unauthorized, skip items + continue + } + appIds = append(appIds, pipeline.AppId) + appNamesMap[pipeline.AppId] = pipeline.App.AppName + pipelineMap[pipeline.Id] = true + } + //authorization block ends here + + if len(appIds) == 0 { + impl.Logger.Warnw("there is no app id found for fetching app workflows", "envId", envId) + return workflows, nil + } + appWorkflow, err := impl.appWorkflowRepository.FindByAppIds(appIds) + if err != nil && err != pg.ErrNoRows { + impl.Logger.Errorw("error fetching app workflows by app ids", "err", err) + return nil, err + } + for _, w := range appWorkflow { + appName := appNamesMap[w.AppId] + workflow := AppWorkflowDto{ + Id: w.Id, + Name: appName, // here workflow name is app name, only for environment app grouping view + AppId: w.AppId, + } + mappings, err := impl.FindAppWorkflowMapping(w.Id) + if err != nil { + impl.Logger.Errorw("error fetching app workflow mapping by wf id", "err", err) + return nil, err + } + valid := false + for _, mapping := range mappings { + if mapping.Type == CD_PIPELINE_TYPE { + if _, ok := pipelineMap[mapping.ComponentId]; ok { + valid = true + } + } + } + //if there is no matching pipeline for requested environment, skip from workflow listing + if valid { + workflow.AppWorkflowMappingDto = mappings + workflows = append(workflows, workflow) + } + } + return workflows, err +} diff --git a/pkg/bean/app.go b/pkg/bean/app.go index 35ebefe4cf..1b5580aa97 100644 --- a/pkg/bean/app.go +++ b/pkg/bean/app.go @@ -472,6 +472,7 @@ type CDPipelineConfigObject struct { ParentPipelineId int `json:"parentPipelineId"` ParentPipelineType string `json:"parentPipelineType"` DeploymentAppType string `json:"deploymentAppType"` + AppName string `json:"appName"` //Downstream []int `json:"downstream"` //PipelineCounter of downstream (for future reference only) } diff --git a/pkg/cluster/EnvironmentService.go b/pkg/cluster/EnvironmentService.go index 0716ba5edf..72d605c3c9 100644 --- a/pkg/cluster/EnvironmentService.go +++ b/pkg/cluster/EnvironmentService.go @@ -44,6 +44,7 @@ type EnvironmentBean struct { Namespace string `json:"namespace,omitempty" validate:"name-space-component,max=50"` CdArgoSetup bool `json:"isClusterCdActive"` EnvironmentIdentifier string `json:"environmentIdentifier"` + AppCount int `json:"appCount"` } type EnvDto struct { @@ -59,6 +60,11 @@ type ClusterEnvDto struct { Environments []*EnvDto `json:"environments,omitempty"` } +type AppGroupingResponse struct { + EnvList []EnvironmentBean `json:"envList"` + EnvCount int `json:"envCount"` +} + type EnvironmentService interface { FindOne(environment string) (*EnvironmentBean, error) Create(mappings *EnvironmentBean, userId int32) (*EnvironmentBean, error) @@ -90,7 +96,7 @@ type EnvironmentServiceImpl struct { func NewEnvironmentServiceImpl(environmentRepository repository.EnvironmentRepository, clusterService ClusterService, logger *zap.SugaredLogger, K8sUtil *util.K8sUtil, k8sInformerFactory informer.K8sInformerFactory, - // propertiesConfigService pipeline.PropertiesConfigService, +// propertiesConfigService pipeline.PropertiesConfigService, userAuthService user.UserAuthService) *EnvironmentServiceImpl { return &EnvironmentServiceImpl{ environmentRepository: environmentRepository, diff --git a/pkg/cluster/repository/EnvironmentRepository.go b/pkg/cluster/repository/EnvironmentRepository.go index 111bcf8071..73b41c139e 100644 --- a/pkg/cluster/repository/EnvironmentRepository.go +++ b/pkg/cluster/repository/EnvironmentRepository.go @@ -43,7 +43,7 @@ type EnvironmentRepository interface { FindOne(environment string) (*Environment, error) Create(mappings *Environment) error FindAll() ([]Environment, error) - FindAllActive() ([]Environment, error) + FindAllActive() ([]*Environment, error) MarkEnvironmentDeleted(mappings *Environment, tx *pg.Tx) error GetConnection() (dbConnection *pg.DB) @@ -59,6 +59,11 @@ type EnvironmentRepository interface { FindByClusterIdAndNamespace(namespaceClusterPair []*ClusterNamespacePair) ([]*Environment, error) FindByClusterIds(clusterIds []int) ([]*Environment, error) FindIdsByNames(envNames []string) ([]int, error) + + FindByEnvName(envName string) ([]*Environment, error) + FindByEnvNameAndClusterIds(envName string, clusterIds []int) ([]*Environment, error) + FindByClusterIdsWithFilter(clusterIds []int) ([]*Environment, error) + FindAllActiveWithFilter() ([]*Environment, error) } func NewEnvironmentRepositoryImpl(dbConnection *pg.DB, logger *zap.SugaredLogger, appStatusRepository appStatus.AppStatusRepository) *EnvironmentRepositoryImpl { @@ -188,6 +193,7 @@ func (repositoryImpl EnvironmentRepositoryImpl) FindByClusterIdAndNamespace(name Select() return mappings, err } + func (repositoryImpl EnvironmentRepositoryImpl) FindByClusterIds(clusterIds []int) ([]*Environment, error) { var mappings []*Environment err := repositoryImpl.dbConnection. @@ -199,8 +205,8 @@ func (repositoryImpl EnvironmentRepositoryImpl) FindByClusterIds(clusterIds []in return mappings, err } -func (repositoryImpl EnvironmentRepositoryImpl) FindAllActive() ([]Environment, error) { - var mappings []Environment +func (repositoryImpl EnvironmentRepositoryImpl) FindAllActive() ([]*Environment, error) { + var mappings []*Environment err := repositoryImpl. dbConnection.Model(&mappings). Where("environment.active = ?", true). @@ -264,3 +270,45 @@ func (repo EnvironmentRepositoryImpl) FindIdsByNames(envNames []string) ([]int, _, err := repo.dbConnection.Query(&ids, query, pg.In(envNames), true) return ids, err } + +func (repositoryImpl EnvironmentRepositoryImpl) FindByEnvName(envName string) ([]*Environment, error) { + var environmentCluster []*Environment + err := repositoryImpl.dbConnection. + Model(&environmentCluster). + Column("environment.*", "Cluster"). + Where("environment_name like ?", "%"+envName+"%"). + Where("environment.active = ?", true). + Order("environment.environment_name ASC").Select() + return environmentCluster, err +} + +func (repositoryImpl EnvironmentRepositoryImpl) FindByEnvNameAndClusterIds(envName string, clusterIds []int) ([]*Environment, error) { + var mappings []*Environment + err := repositoryImpl.dbConnection. + Model(&mappings). + Column("environment.*", "Cluster"). + Where("environment_name like ?", "%"+envName+"%"). + Where("environment.active = true"). + Where("environment.cluster_id in (?)", pg.In(clusterIds)). + Order("environment.environment_name ASC").Select() + return mappings, err +} + +func (repositoryImpl EnvironmentRepositoryImpl) FindByClusterIdsWithFilter(clusterIds []int) ([]*Environment, error) { + var mappings []*Environment + err := repositoryImpl.dbConnection.Model(&mappings). + Column("environment.*", "Cluster"). + Where("environment.active = true"). + Where("environment.cluster_id in (?)", pg.In(clusterIds)). + Order("environment.environment_name ASC").Select() + return mappings, err +} + +func (repositoryImpl EnvironmentRepositoryImpl) FindAllActiveWithFilter() ([]*Environment, error) { + var mappings []*Environment + err := repositoryImpl.dbConnection.Model(&mappings). + Column("environment.*", "Cluster"). + Where("environment.active = ?", true). + Order("environment.environment_name ASC").Select() + return mappings, err +} diff --git a/pkg/pipeline/CdHandler.go b/pkg/pipeline/CdHandler.go index 2122acc026..8f0d277956 100644 --- a/pkg/pipeline/CdHandler.go +++ b/pkg/pipeline/CdHandler.go @@ -41,6 +41,7 @@ import ( "github.com/devtron-labs/devtron/pkg/user" util3 "github.com/devtron-labs/devtron/util" "github.com/devtron-labs/devtron/util/argo" + "github.com/devtron-labs/devtron/util/rbac" "github.com/go-pg/pg" "go.uber.org/zap" "os" @@ -57,12 +58,14 @@ type CdHandler interface { DownloadCdWorkflowArtifacts(pipelineId int, buildId int) (*os.File, error) FetchCdPrePostStageStatus(pipelineId int) ([]pipelineConfig.CdWorkflowWithArtifact, error) CancelStage(workflowRunnerId int, userId int32) (int, error) - FetchAppWorkflowStatusForTriggerView(pipelineId int) ([]*pipelineConfig.CdWorkflowStatus, error) + FetchAppWorkflowStatusForTriggerView(appId int) ([]*pipelineConfig.CdWorkflowStatus, error) CheckHelmAppStatusPeriodicallyAndUpdateInDb(helmPipelineStatusCheckEligibleTime int) error CheckArgoAppStatusPeriodicallyAndUpdateInDb(timeForDegradation int) error CheckArgoPipelineTimelineStatusPeriodicallyAndUpdateInDb(pendingSinceSeconds int, timeForDegradation int) error UpdatePipelineTimelineAndStatusByLiveApplicationFetch(pipeline *pipelineConfig.Pipeline, userId int32) (err error, isTimelineUpdated bool) CheckAndSendArgoPipelineStatusSyncEventIfNeeded(pipelineId int, userId int32) + FetchAppWorkflowStatusForTriggerViewForEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]*pipelineConfig.CdWorkflowStatus, error) + FetchAppDeploymentStatusForEnvironments(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]*pipelineConfig.AppDeploymentStatus, error) } type CdHandlerImpl struct { @@ -93,6 +96,7 @@ type CdHandlerImpl struct { pipelineStatusTimelineService app.PipelineStatusTimelineService appService app.AppService appStatusService app_status.AppStatusService + enforcerUtil rbac.EnforcerUtil } func NewCdHandlerImpl(Logger *zap.SugaredLogger, cdConfig *CdConfig, userService user.UserService, @@ -115,7 +119,7 @@ func NewCdHandlerImpl(Logger *zap.SugaredLogger, cdConfig *CdConfig, userService pipelineStatusSyncDetailService app.PipelineStatusSyncDetailService, pipelineStatusTimelineService app.PipelineStatusTimelineService, appService app.AppService, - appStatusService app_status.AppStatusService) *CdHandlerImpl { + appStatusService app_status.AppStatusService, enforcerUtil rbac.EnforcerUtil) *CdHandlerImpl { return &CdHandlerImpl{ Logger: Logger, cdConfig: cdConfig, @@ -144,6 +148,7 @@ func NewCdHandlerImpl(Logger *zap.SugaredLogger, cdConfig *CdConfig, userService pipelineStatusTimelineService: pipelineStatusTimelineService, appService: appService, appStatusService: appStatusService, + enforcerUtil: enforcerUtil, } } @@ -152,6 +157,12 @@ type ArgoPipelineStatusSyncEvent struct { UserId int32 `json:"userId"` } +const NotTriggered string = "Not Triggered" +const NotDeployed = "Not Deployed" +const WorklowTypeDeploy = "DEPLOY" +const WorklowTypePre = "PRE" +const WorklowTypePost = "POST" + func (impl *CdHandlerImpl) CheckArgoAppStatusPeriodicallyAndUpdateInDb(deployedBeforeMinutes int) error { pipelines, err := impl.pipelineRepository.GetArgoPipelinesHavingLatestTriggerStuckInNonTerminalStatuses(deployedBeforeMinutes) if err != nil { @@ -882,11 +893,144 @@ func (impl *CdHandlerImpl) FetchAppWorkflowStatusForTriggerView(appId int) ([]*p cdWorkflowStatus := &pipelineConfig.CdWorkflowStatus{} cdWorkflowStatus.PipelineId = item.PipelineId cdWorkflowStatus.CiPipelineId = item.CiPipelineId - if item.WorkflowType == "PRE" { + if item.WorkflowType == WorklowTypePre { + cdWorkflowStatus.PreStatus = statusMap[item.WfrId] + } else if item.WorkflowType == WorklowTypeDeploy { + cdWorkflowStatus.DeployStatus = statusMap[item.WfrId] + } else if item.WorkflowType == WorklowTypePost { + cdWorkflowStatus.PostStatus = statusMap[item.WfrId] + } + cdMap[item.PipelineId] = cdWorkflowStatus + } else { + cdWorkflowStatus := cdMap[item.PipelineId] + cdWorkflowStatus.PipelineId = item.PipelineId + cdWorkflowStatus.CiPipelineId = item.CiPipelineId + if item.WorkflowType == WorklowTypePre { + cdWorkflowStatus.PreStatus = statusMap[item.WfrId] + } else if item.WorkflowType == WorklowTypeDeploy { + cdWorkflowStatus.DeployStatus = statusMap[item.WfrId] + } else if item.WorkflowType == WorklowTypePost { + cdWorkflowStatus.PostStatus = statusMap[item.WfrId] + } + cdMap[item.PipelineId] = cdWorkflowStatus + } + } + + for _, item := range cdMap { + if item.PreStatus == "" { + item.PreStatus = NotTriggered + } + if item.DeployStatus == "" { + item.DeployStatus = NotDeployed + } + if item.PostStatus == "" { + item.PostStatus = NotTriggered + } + cdWorkflowStatus = append(cdWorkflowStatus, item) + } + + if len(cdWorkflowStatus) == 0 { + for _, item := range pipelineIds { + cdWs := &pipelineConfig.CdWorkflowStatus{} + cdWs.PipelineId = item + cdWs.PreStatus = NotTriggered + cdWs.DeployStatus = NotDeployed + cdWs.PostStatus = NotTriggered + cdWorkflowStatus = append(cdWorkflowStatus, cdWs) + } + } else { + for _, item := range pipelineIds { + if _, ok := cdMap[item]; !ok { + cdWs := &pipelineConfig.CdWorkflowStatus{} + cdWs.PipelineId = item + cdWs.PreStatus = NotTriggered + cdWs.DeployStatus = NotDeployed + cdWs.PostStatus = NotTriggered + cdWorkflowStatus = append(cdWorkflowStatus, cdWs) + } + } + } + + return cdWorkflowStatus, err +} + +func (impl *CdHandlerImpl) FetchAppWorkflowStatusForTriggerViewForEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]*pipelineConfig.CdWorkflowStatus, error) { + cdWorkflowStatus := make([]*pipelineConfig.CdWorkflowStatus, 0) + pipelines, err := impl.pipelineRepository.FindActiveByEnvId(envId) + if err != nil && err != pg.ErrNoRows { + impl.Logger.Errorw("error fetching pipelines for env id", "err", err) + return nil, err + } + + var appIds []int + for _, pipeline := range pipelines { + appIds = append(appIds, pipeline.AppId) + } + if len(appIds) == 0 { + impl.Logger.Warnw("there is no app id found for fetching cd pipelines", "envId", envId) + return cdWorkflowStatus, nil + } + pipelines, err = impl.pipelineRepository.FindActiveByAppIds(appIds) + if err != nil && err != pg.ErrNoRows { + return cdWorkflowStatus, err + } + pipelineIds := make([]int, 0) + //authorization block starts here + var envObjectArr []string + var appObjectArr []string + rbacObjectMap := make(map[int][]string) + for _, pipeline := range pipelines { + appObject := impl.enforcerUtil.GetAppRBACName(pipeline.App.AppName) + envObject := impl.enforcerUtil.GetEnvRBACNameByCdPipelineIdAndEnvId(pipeline.Id) + appObjectArr = append(appObjectArr, appObject) + envObjectArr = append(envObjectArr, envObject) + rbacObjectMap[pipeline.Id] = []string{appObject, envObject} + } + for _, pipeline := range pipelines { + appResults, envResults := checkAuthBatch(emailId, appObjectArr, envObjectArr) + appObject := rbacObjectMap[pipeline.Id][0] + envObject := rbacObjectMap[pipeline.Id][1] + if !(appResults[appObject] && envResults[envObject]) { + //if user unauthorized, skip items + continue + } + pipelineIds = append(pipelineIds, pipeline.Id) + } + //authorization block ends here + if len(pipelineIds) == 0 { + return cdWorkflowStatus, nil + } + cdMap := make(map[int]*pipelineConfig.CdWorkflowStatus) + result, err := impl.cdWorkflowRepository.FetchAllCdStagesLatestEntity(pipelineIds) + if err != nil { + return cdWorkflowStatus, err + } + var wfrIds []int + for _, item := range result { + wfrIds = append(wfrIds, item.WfrId) + } + + statusMap := make(map[int]string) + if len(wfrIds) > 0 { + wfrList, err := impl.cdWorkflowRepository.FetchAllCdStagesLatestEntityStatus(wfrIds) + if err != nil && !util.IsErrNoRows(err) { + return cdWorkflowStatus, err + } + for _, item := range wfrList { + statusMap[item.Id] = item.Status + } + } + + for _, item := range result { + if _, ok := cdMap[item.PipelineId]; !ok { + cdWorkflowStatus := &pipelineConfig.CdWorkflowStatus{} + cdWorkflowStatus.PipelineId = item.PipelineId + cdWorkflowStatus.CiPipelineId = item.CiPipelineId + if item.WorkflowType == WorklowTypePre { cdWorkflowStatus.PreStatus = statusMap[item.WfrId] - } else if item.WorkflowType == "DEPLOY" { + } else if item.WorkflowType == WorklowTypeDeploy { cdWorkflowStatus.DeployStatus = statusMap[item.WfrId] - } else if item.WorkflowType == "POST" { + } else if item.WorkflowType == WorklowTypePost { cdWorkflowStatus.PostStatus = statusMap[item.WfrId] } cdMap[item.PipelineId] = cdWorkflowStatus @@ -894,11 +1038,11 @@ func (impl *CdHandlerImpl) FetchAppWorkflowStatusForTriggerView(appId int) ([]*p cdWorkflowStatus := cdMap[item.PipelineId] cdWorkflowStatus.PipelineId = item.PipelineId cdWorkflowStatus.CiPipelineId = item.CiPipelineId - if item.WorkflowType == "PRE" { + if item.WorkflowType == WorklowTypePre { cdWorkflowStatus.PreStatus = statusMap[item.WfrId] - } else if item.WorkflowType == "DEPLOY" { + } else if item.WorkflowType == WorklowTypeDeploy { cdWorkflowStatus.DeployStatus = statusMap[item.WfrId] - } else if item.WorkflowType == "POST" { + } else if item.WorkflowType == WorklowTypePre { cdWorkflowStatus.PostStatus = statusMap[item.WfrId] } cdMap[item.PipelineId] = cdWorkflowStatus @@ -907,13 +1051,13 @@ func (impl *CdHandlerImpl) FetchAppWorkflowStatusForTriggerView(appId int) ([]*p for _, item := range cdMap { if item.PreStatus == "" { - item.PreStatus = "Not Triggered" + item.PreStatus = NotTriggered } if item.DeployStatus == "" { - item.DeployStatus = "Not Deployed" + item.DeployStatus = NotDeployed } if item.PostStatus == "" { - item.PostStatus = "Not Triggered" + item.PostStatus = NotTriggered } cdWorkflowStatus = append(cdWorkflowStatus, item) } @@ -922,9 +1066,9 @@ func (impl *CdHandlerImpl) FetchAppWorkflowStatusForTriggerView(appId int) ([]*p for _, item := range pipelineIds { cdWs := &pipelineConfig.CdWorkflowStatus{} cdWs.PipelineId = item - cdWs.PreStatus = "Not Triggered" - cdWs.DeployStatus = "Not Deployed" - cdWs.PostStatus = "Not Triggered" + cdWs.PreStatus = NotTriggered + cdWs.DeployStatus = NotDeployed + cdWs.PostStatus = NotTriggered cdWorkflowStatus = append(cdWorkflowStatus, cdWs) } } else { @@ -932,9 +1076,9 @@ func (impl *CdHandlerImpl) FetchAppWorkflowStatusForTriggerView(appId int) ([]*p if _, ok := cdMap[item]; !ok { cdWs := &pipelineConfig.CdWorkflowStatus{} cdWs.PipelineId = item - cdWs.PreStatus = "Not Triggered" - cdWs.DeployStatus = "Not Deployed" - cdWs.PostStatus = "Not Triggered" + cdWs.PreStatus = NotTriggered + cdWs.DeployStatus = NotDeployed + cdWs.PostStatus = WorklowTypeDeploy cdWorkflowStatus = append(cdWorkflowStatus, cdWs) } } @@ -942,3 +1086,96 @@ func (impl *CdHandlerImpl) FetchAppWorkflowStatusForTriggerView(appId int) ([]*p return cdWorkflowStatus, err } + +func (impl *CdHandlerImpl) FetchAppDeploymentStatusForEnvironments(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]*pipelineConfig.AppDeploymentStatus, error) { + deploymentStatuses := make([]*pipelineConfig.AppDeploymentStatus, 0) + deploymentStatusesMap := make(map[int]*pipelineConfig.AppDeploymentStatus) + pipelineAppMap := make(map[int]int) + statusMap := make(map[int]string) + + pipelines, err := impl.pipelineRepository.FindActiveByEnvId(envId) + if err != nil && err != pg.ErrNoRows { + impl.Logger.Errorw("error fetching pipelines for env id", "err", err) + return nil, err + } + for _, pipeline := range pipelines { + pipelineAppMap[pipeline.Id] = pipeline.AppId + } + pipelineIds := make([]int, 0) + + //authorization block starts here + var envObjectArr []string + var appObjectArr []string + rbacObjectMap := make(map[int][]string) + for _, pipeline := range pipelines { + appObject := impl.enforcerUtil.GetAppRBACName(pipeline.App.AppName) + envObject := impl.enforcerUtil.GetEnvRBACNameByCdPipelineIdAndEnvId(pipeline.Id) + appObjectArr = append(appObjectArr, appObject) + envObjectArr = append(envObjectArr, envObject) + rbacObjectMap[pipeline.Id] = []string{appObject, envObject} + } + appResults, envResults := checkAuthBatch(emailId, appObjectArr, envObjectArr) + for _, pipeline := range pipelines { + appObject := rbacObjectMap[pipeline.Id][0] + envObject := rbacObjectMap[pipeline.Id][1] + if !(appResults[appObject] && envResults[envObject]) { + //if user unauthorized, skip items + continue + } + + pipelineIds = append(pipelineIds, pipeline.Id) + } + //authorization block ends here + + if len(pipelineIds) == 0 { + return deploymentStatuses, nil + } + result, err := impl.cdWorkflowRepository.FetchAllCdStagesLatestEntity(pipelineIds) + if err != nil { + return deploymentStatuses, err + } + var wfrIds []int + for _, item := range result { + wfrIds = append(wfrIds, item.WfrId) + } + if len(wfrIds) > 0 { + wfrList, err := impl.cdWorkflowRepository.FetchAllCdStagesLatestEntityStatus(wfrIds) + if err != nil && !util.IsErrNoRows(err) { + return deploymentStatuses, err + } + for _, item := range wfrList { + if item.Status == "" { + statusMap[item.Id] = NotDeployed + } else { + statusMap[item.Id] = item.Status + } + } + } + + for _, item := range result { + if _, ok := deploymentStatusesMap[item.PipelineId]; !ok { + deploymentStatus := &pipelineConfig.AppDeploymentStatus{} + deploymentStatus.PipelineId = item.PipelineId + if item.WorkflowType == WorklowTypeDeploy { + deploymentStatus.DeployStatus = statusMap[item.WfrId] + deploymentStatus.AppId = pipelineAppMap[deploymentStatus.PipelineId] + deploymentStatusesMap[item.PipelineId] = deploymentStatus + } + } + } + //in case there is no workflow found for pipeline, set all the pipeline status - Not Deployed + for _, pipelineId := range pipelineIds { + if _, ok := deploymentStatusesMap[pipelineId]; !ok { + deploymentStatus := &pipelineConfig.AppDeploymentStatus{} + deploymentStatus.PipelineId = pipelineId + deploymentStatus.DeployStatus = NotDeployed + deploymentStatus.AppId = pipelineAppMap[deploymentStatus.PipelineId] + deploymentStatusesMap[pipelineId] = deploymentStatus + } + } + for _, deploymentStatus := range deploymentStatusesMap { + deploymentStatuses = append(deploymentStatuses, deploymentStatus) + } + + return deploymentStatuses, err +} diff --git a/pkg/pipeline/CiCdPipelineOrchestrator.go b/pkg/pipeline/CiCdPipelineOrchestrator.go index 47d3927298..9abd3c760a 100644 --- a/pkg/pipeline/CiCdPipelineOrchestrator.go +++ b/pkg/pipeline/CiCdPipelineOrchestrator.go @@ -75,6 +75,7 @@ type CiCdPipelineOrchestrator interface { AddPipelineMaterialInGitSensor(pipelineMaterials []*pipelineConfig.CiPipelineMaterial) error CheckStringMatchRegex(regex string, value string) bool CreateEcrRepo(dockerRepository, AWSRegion, AWSAccessKeyId, AWSSecretAccessKey string) error + GetCdPipelinesForEnv(envId int) (cdPipelines *bean.CdPipelines, err error) } type CiCdPipelineOrchestratorImpl struct { @@ -1359,6 +1360,87 @@ func (impl CiCdPipelineOrchestratorImpl) GetCdPipelinesForApp(appId int) (cdPipe return cdPipelines, err } +func (impl CiCdPipelineOrchestratorImpl) GetCdPipelinesForEnv(envId int) (cdPipelines *bean.CdPipelines, err error) { + dbPipelines, err := impl.pipelineRepository.FindActiveByEnvId(envId) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error in fetching cdPipeline", "envId", envId, "err", err) + return nil, err + } + var appIds []int + for _, pipeline := range dbPipelines { + appIds = append(appIds, pipeline.AppId) + } + if len(appIds) == 0 { + err = &util.ApiError{Code: "404", HttpStatusCode: 200, UserMessage: "no cd pipeline found"} + return cdPipelines, err + } + dbPipelines, err = impl.pipelineRepository.FindActiveByAppIds(appIds) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error fetching pipelines for env id", "err", err) + return nil, err + } + + var pipelines []*bean.CDPipelineConfigObject + for _, dbPipeline := range dbPipelines { + preStage := bean.CdStage{} + if len(dbPipeline.PreStageConfig) > 0 { + preStage.Name = "Pre-Deployment" + preStage.Config = dbPipeline.PreStageConfig + preStage.TriggerType = dbPipeline.PreTriggerType + } + postStage := bean.CdStage{} + if len(dbPipeline.PostStageConfig) > 0 { + postStage.Name = "Post-Deployment" + postStage.Config = dbPipeline.PostStageConfig + postStage.TriggerType = dbPipeline.PostTriggerType + } + + preStageConfigmapSecrets := bean.PreStageConfigMapSecretNames{} + postStageConfigmapSecrets := bean.PostStageConfigMapSecretNames{} + + if dbPipeline.PreStageConfigMapSecretNames != "" { + err = json.Unmarshal([]byte(dbPipeline.PreStageConfigMapSecretNames), &preStageConfigmapSecrets) + if err != nil { + impl.logger.Errorw("unmarshal error", "err", err) + return nil, err + } + } + if dbPipeline.PostStageConfigMapSecretNames != "" { + err = json.Unmarshal([]byte(dbPipeline.PostStageConfigMapSecretNames), &postStageConfigmapSecrets) + if err != nil { + impl.logger.Errorw("unmarshal error", "err", err) + return nil, err + } + } + + pipeline := &bean.CDPipelineConfigObject{ + Id: dbPipeline.Id, + Name: dbPipeline.Name, + EnvironmentId: dbPipeline.EnvironmentId, + CiPipelineId: dbPipeline.CiPipelineId, + TriggerType: dbPipeline.TriggerType, + PreStage: preStage, + PostStage: postStage, + RunPreStageInEnv: dbPipeline.RunPreStageInEnv, + RunPostStageInEnv: dbPipeline.RunPostStageInEnv, + PreStageConfigMapSecretNames: preStageConfigmapSecrets, + PostStageConfigMapSecretNames: postStageConfigmapSecrets, + DeploymentAppType: dbPipeline.DeploymentAppType, + AppName: dbPipeline.App.AppName, + } + pipelines = append(pipelines, pipeline) + } + cdPipelines = &bean.CdPipelines{ + Pipelines: pipelines, + } + if len(pipelines) == 0 { + err = &util.ApiError{Code: "404", HttpStatusCode: 200, UserMessage: "no cd pipeline found"} + } else { + err = nil + } + return cdPipelines, err +} + func (impl CiCdPipelineOrchestratorImpl) GetCdPipelinesForAppAndEnv(appId int, envId int) (cdPipelines *bean.CdPipelines, err error) { dbPipelines, err := impl.pipelineRepository.FindActiveByAppIdAndEnvironmentId(appId, envId) if err != nil { diff --git a/pkg/pipeline/CiHandler.go b/pkg/pipeline/CiHandler.go index 89b61b4bc4..3cb5f8ecf8 100644 --- a/pkg/pipeline/CiHandler.go +++ b/pkg/pipeline/CiHandler.go @@ -25,6 +25,7 @@ import ( "fmt" blob_storage "github.com/devtron-labs/common-lib/blob-storage" bean2 "github.com/devtron-labs/devtron/api/bean" + "github.com/devtron-labs/devtron/util/rbac" "io/ioutil" errors2 "k8s.io/apimachinery/pkg/api/errors" "net/http" @@ -69,6 +70,7 @@ type CiHandler interface { FetchMaterialInfoByArtifactId(ciArtifactId int) (*GitTriggerInfoResponse, error) WriteToCreateTestSuites(pipelineId int, buildId int, triggeredBy int) UpdateCiWorkflowStatusFailure(timeoutForFailureCiBuild int) error + FetchCiStatusForTriggerViewForEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]*pipelineConfig.CiWorkflowStatus, error) } type CiHandlerImpl struct { @@ -87,13 +89,15 @@ type CiHandlerImpl struct { ciPipelineRepository pipelineConfig.CiPipelineRepository appListingRepository repository.AppListingRepository K8sUtil *util.K8sUtil + cdPipelineRepository pipelineConfig.PipelineRepository + enforcerUtil rbac.EnforcerUtil } func NewCiHandlerImpl(Logger *zap.SugaredLogger, ciService CiService, ciPipelineMaterialRepository pipelineConfig.CiPipelineMaterialRepository, gitSensorClient gitSensor.GitSensorClient, ciWorkflowRepository pipelineConfig.CiWorkflowRepository, workflowService WorkflowService, ciLogService CiLogService, ciConfig *CiConfig, ciArtifactRepository repository.CiArtifactRepository, userService user.UserService, eventClient client.EventClient, eventFactory client.EventFactory, ciPipelineRepository pipelineConfig.CiPipelineRepository, appListingRepository repository.AppListingRepository, - K8sUtil *util.K8sUtil) *CiHandlerImpl { + K8sUtil *util.K8sUtil, cdPipelineRepository pipelineConfig.PipelineRepository, enforcerUtil rbac.EnforcerUtil) *CiHandlerImpl { return &CiHandlerImpl{ Logger: Logger, ciService: ciService, @@ -110,6 +114,8 @@ func NewCiHandlerImpl(Logger *zap.SugaredLogger, ciService CiService, ciPipeline ciPipelineRepository: ciPipelineRepository, appListingRepository: appListingRepository, K8sUtil: K8sUtil, + cdPipelineRepository: cdPipelineRepository, + enforcerUtil: enforcerUtil, } } @@ -1272,3 +1278,64 @@ func (impl *CiHandlerImpl) UpdateCiWorkflowStatusFailure(timeoutForFailureCiBuil } return nil } + +func (impl *CiHandlerImpl) FetchCiStatusForTriggerViewForEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]*pipelineConfig.CiWorkflowStatus, error) { + ciWorkflowStatuses := make([]*pipelineConfig.CiWorkflowStatus, 0) + cdPipelines, err := impl.cdPipelineRepository.FindActiveByEnvId(envId) + if err != nil && err != pg.ErrNoRows { + impl.Logger.Errorw("error fetching pipelines for env id", "err", err) + return nil, err + } + var appIds []int + for _, pipeline := range cdPipelines { + appIds = append(appIds, pipeline.AppId) + } + if len(appIds) == 0 { + impl.Logger.Warnw("there is no app id found for fetching ci pipelines", "envId", envId) + return ciWorkflowStatuses, nil + } + pipelines, err := impl.ciPipelineRepository.FindByAppIds(appIds) + if err != nil && err != pg.ErrNoRows { + impl.Logger.Errorw("error in fetching ci pipeline", "err", err) + return ciWorkflowStatuses, err + } + + //authorization block starts here + var appObjectArr []string + rbacObjectMap := make(map[int][]string) + for _, pipeline := range pipelines { + appObject := impl.enforcerUtil.GetAppRBACName(pipeline.App.AppName) + appObjectArr = append(appObjectArr, appObject) + rbacObjectMap[pipeline.Id] = []string{appObject, ""} + } + appResults, _ := checkAuthBatch(emailId, appObjectArr, []string{}) + for _, pipeline := range pipelines { + appObject := rbacObjectMap[pipeline.Id][0] //here only app permission have to check + if !appResults[appObject] { + //if user unauthorized, skip items + continue + } + pipelineId := 0 + if pipeline.ParentCiPipeline == 0 { + pipelineId = pipeline.Id + } else { + pipelineId = pipeline.ParentCiPipeline + } + workflow, err := impl.ciWorkflowRepository.FindLastTriggeredWorkflow(pipelineId) + if err != nil && !util.IsErrNoRows(err) { + impl.Logger.Errorw("err", "pipelineId", pipelineId, "err", err) + return ciWorkflowStatuses, err + } + ciWorkflowStatus := &pipelineConfig.CiWorkflowStatus{} + ciWorkflowStatus.CiPipelineId = pipeline.Id + if workflow.Id > 0 { + ciWorkflowStatus.CiPipelineName = workflow.CiPipeline.Name + ciWorkflowStatus.CiStatus = workflow.Status + ciWorkflowStatus.StorageConfigured = workflow.BlobStorageEnabled + } else { + ciWorkflowStatus.CiStatus = "Not Triggered" + } + ciWorkflowStatuses = append(ciWorkflowStatuses, ciWorkflowStatus) + } + return ciWorkflowStatuses, nil +} diff --git a/pkg/pipeline/GitopsOrHelmOption_test.go b/pkg/pipeline/GitopsOrHelmOption_test.go index b250809dc2..95fa4a4310 100644 --- a/pkg/pipeline/GitopsOrHelmOption_test.go +++ b/pkg/pipeline/GitopsOrHelmOption_test.go @@ -24,7 +24,8 @@ func TestGitopsOrHelmOption(t *testing.T) { nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: false}, nil) + nil, nil, nil, nil, nil, + nil, nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: false}, nil, nil) pipelineCreateRequest := &bean.CdPipelines{ Pipelines: []*bean.CDPipelineConfigObject{ @@ -77,7 +78,9 @@ func TestGitopsOrHelmOption(t *testing.T) { nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: false}, nil) + nil, nil, nil, nil, + nil, nil, nil, nil, + &DeploymentServiceTypeConfig{IsInternalUse: false}, nil, nil) pipelineCreateRequest := &bean.CdPipelines{ Pipelines: []*bean.CDPipelineConfigObject{ @@ -130,7 +133,8 @@ func TestGitopsOrHelmOption(t *testing.T) { nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: true}, nil) + nil, nil, nil, nil, nil, nil, + nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: true}, nil, nil) pipelineCreateRequestHelm := &bean.CdPipelines{ Pipelines: []*bean.CDPipelineConfigObject{ @@ -221,7 +225,8 @@ func TestGitopsOrHelmOption(t *testing.T) { nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: false}, nil) + nil, nil, nil, nil, nil, nil, + nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: false}, nil, nil) pipelineCreateRequest := &bean.CdPipelines{ Pipelines: []*bean.CDPipelineConfigObject{ @@ -278,7 +283,8 @@ func TestGitopsOrHelmOption(t *testing.T) { nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: true}, nil) + nil, nil, nil, nil, nil, nil, + nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: true}, nil, nil) pipelineCreateRequest := &bean.CdPipelines{ Pipelines: []*bean.CDPipelineConfigObject{ diff --git a/pkg/pipeline/PipelineBuilder.go b/pkg/pipeline/PipelineBuilder.go index db5f5c840e..ac2e6cd233 100644 --- a/pkg/pipeline/PipelineBuilder.go +++ b/pkg/pipeline/PipelineBuilder.go @@ -27,6 +27,7 @@ import ( "github.com/devtron-labs/devtron/internal/sql/repository/security" "github.com/devtron-labs/devtron/pkg/chart" chartRepoRepository "github.com/devtron-labs/devtron/pkg/chartRepo/repository" + "github.com/devtron-labs/devtron/pkg/cluster" repository2 "github.com/devtron-labs/devtron/pkg/cluster/repository" bean3 "github.com/devtron-labs/devtron/pkg/pipeline/bean" "github.com/devtron-labs/devtron/pkg/pipeline/history" @@ -34,6 +35,7 @@ import ( "github.com/devtron-labs/devtron/pkg/sql" "github.com/devtron-labs/devtron/pkg/user" util3 "github.com/devtron-labs/devtron/pkg/util" + "github.com/devtron-labs/devtron/util/rbac" "net/http" "net/url" "sort" @@ -127,6 +129,11 @@ type PipelineBuilder interface { DeleteCiPipeline(request *bean.CiPatchRequest) (*bean.CiPipeline, error) IsGitOpsRequiredForCD(pipelineCreateRequest *bean.CdPipelines) bool SetPipelineDeploymentAppType(pipelineCreateRequest *bean.CdPipelines, isGitOpsConfigured bool) + GetCiPipelineByEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]*bean.CiConfigRequest, error) + GetCdPipelinesByEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) (cdPipelines *bean.CdPipelines, err error) + GetExternalCiByEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) (ciConfig []*bean.ExternalCiConfig, err error) + GetEnvironmentListForAutocompleteFilter(envName string, clusterIds []int, offset int, size int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) (*cluster.AppGroupingResponse, error) + GetAppListForEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]*AppBean, error) } type PipelineBuilderImpl struct { @@ -181,6 +188,7 @@ type PipelineBuilderImpl struct { globalStrategyMetadataChartRefMappingRepository chartRepoRepository.GlobalStrategyMetadataChartRefMappingRepository deploymentConfig *DeploymentServiceTypeConfig appStatusRepository appStatus.AppStatusRepository + enforcerUtil rbac.EnforcerUtil } func NewPipelineBuilderImpl(logger *zap.SugaredLogger, @@ -226,7 +234,8 @@ func NewPipelineBuilderImpl(logger *zap.SugaredLogger, CiPipelineHistoryService history.CiPipelineHistoryService, globalStrategyMetadataRepository chartRepoRepository.GlobalStrategyMetadataRepository, globalStrategyMetadataChartRefMappingRepository chartRepoRepository.GlobalStrategyMetadataChartRefMappingRepository, - deploymentConfig *DeploymentServiceTypeConfig, appStatusRepository appStatus.AppStatusRepository) *PipelineBuilderImpl { + deploymentConfig *DeploymentServiceTypeConfig, appStatusRepository appStatus.AppStatusRepository, + enforcerUtil rbac.EnforcerUtil) *PipelineBuilderImpl { return &PipelineBuilderImpl{ logger: logger, ciCdPipelineOrchestrator: ciCdPipelineOrchestrator, @@ -279,6 +288,7 @@ func NewPipelineBuilderImpl(logger *zap.SugaredLogger, globalStrategyMetadataChartRefMappingRepository: globalStrategyMetadataChartRefMappingRepository, deploymentConfig: deploymentConfig, appStatusRepository: appStatusRepository, + enforcerUtil: enforcerUtil, } } @@ -3035,3 +3045,450 @@ func (impl PipelineBuilderImpl) buildResponses() []bean.ResponseSchemaObject { responseSchemaObjects = append(responseSchemaObjects, response401) return responseSchemaObjects } + +func (impl PipelineBuilderImpl) GetCiPipelineByEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]*bean.CiConfigRequest, error) { + ciPipelines, err := impl.pipelineRepository.FindActiveByEnvId(envId) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error fetching pipelines for env id", "err", err) + return nil, err + } + var appIds []int + //authorization block starts here + var appObjectArr []string + rbacObjectMap := make(map[int][]string) + for _, pipeline := range ciPipelines { + appObject := impl.enforcerUtil.GetAppRBACName(pipeline.App.AppName) + appObjectArr = append(appObjectArr, appObject) + rbacObjectMap[pipeline.Id] = []string{appObject, ""} + } + appResults, _ := checkAuthBatch(emailId, appObjectArr, []string{}) //here only app permission need to check + for _, pipeline := range ciPipelines { + appObject := rbacObjectMap[pipeline.Id][0] + if !appResults[appObject] { + //if user unauthorized, skip items + continue + } + appIds = append(appIds, pipeline.AppId) + } + //authorization block ends here + + ciConfigs := make([]*bean.CiConfigRequest, 0) + var ciPipelineResp []*bean.CiPipeline + for _, appId := range appIds { + ciConfig, err := impl.getCiTemplateVariables(appId) + if err != nil { + impl.logger.Debugw("error in fetching ci pipeline", "appId", appId, "err", err) + return nil, err + } + //TODO fill these variables + //--------pipeline population start + ciPipelines, err := impl.ciPipelineRepository.FindByAppId(appId) + if err != nil && !util.IsErrNoRows(err) { + impl.logger.Errorw("error in fetching ci pipeline", "appId", appId, "err", err) + return nil, err + } + + if impl.ciConfig.ExternalCiWebhookUrl == "" { + hostUrl, err := impl.attributesService.GetByKey(attributes.HostUrlKey) + if err != nil { + return nil, err + } + if hostUrl != nil { + impl.ciConfig.ExternalCiWebhookUrl = fmt.Sprintf("%s/%s", hostUrl.Value, ExternalCiWebhookPath) + } + } + //map of ciPipelineId and their templateOverrideConfig + ciOverrideTemplateMap := make(map[int]*bean3.CiTemplateBean) + ciTemplateBeanOverrides, err := impl.ciTemplateService.FindTemplateOverrideByAppId(appId) + if err != nil { + return nil, err + } + + for _, templateBeanOverride := range ciTemplateBeanOverrides { + ciTemplateOverride := templateBeanOverride.CiTemplateOverride + ciOverrideTemplateMap[ciTemplateOverride.CiPipelineId] = templateBeanOverride + } + for _, pipeline := range ciPipelines { + + dockerArgs := make(map[string]string) + if len(pipeline.DockerArgs) > 0 { + err := json.Unmarshal([]byte(pipeline.DockerArgs), &dockerArgs) + if err != nil { + impl.logger.Warnw("error in unmarshal", "err", err) + } + } + + var externalCiConfig bean.ExternalCiConfig + + ciPipelineScripts, err := impl.ciPipelineRepository.FindCiScriptsByCiPipelineId(pipeline.Id) + if err != nil && !util.IsErrNoRows(err) { + impl.logger.Errorw("error in fetching ci scripts") + return nil, err + } + + var beforeDockerBuildScripts []*bean.CiScript + var afterDockerBuildScripts []*bean.CiScript + for _, ciScript := range ciPipelineScripts { + ciScriptResp := &bean.CiScript{ + Id: ciScript.Id, + Index: ciScript.Index, + Name: ciScript.Name, + Script: ciScript.Script, + OutputLocation: ciScript.OutputLocation, + } + if ciScript.Stage == BEFORE_DOCKER_BUILD { + beforeDockerBuildScripts = append(beforeDockerBuildScripts, ciScriptResp) + } else if ciScript.Stage == AFTER_DOCKER_BUILD { + afterDockerBuildScripts = append(afterDockerBuildScripts, ciScriptResp) + } + } + parentCiPipeline, err := impl.ciPipelineRepository.FindById(pipeline.ParentCiPipeline) + if err != nil && !util.IsErrNoRows(err) { + impl.logger.Errorw("err", err) + return nil, err + } + ciPipeline := &bean.CiPipeline{ + Id: pipeline.Id, + Version: pipeline.Version, + Name: pipeline.Name, + Active: pipeline.Active, + Deleted: pipeline.Deleted, + DockerArgs: dockerArgs, + IsManual: pipeline.IsManual, + IsExternal: pipeline.IsExternal, + ParentCiPipeline: pipeline.ParentCiPipeline, + ParentAppId: parentCiPipeline.AppId, + ExternalCiConfig: externalCiConfig, + BeforeDockerBuildScripts: beforeDockerBuildScripts, + AfterDockerBuildScripts: afterDockerBuildScripts, + ScanEnabled: pipeline.ScanEnabled, + IsDockerConfigOverridden: pipeline.IsDockerConfigOverridden, + } + if ciTemplateBean, ok := ciOverrideTemplateMap[pipeline.Id]; ok { + templateOverride := ciTemplateBean.CiTemplateOverride + ciPipeline.DockerConfigOverride = bean.DockerConfigOverride{ + DockerRegistry: templateOverride.DockerRegistryId, + DockerRepository: templateOverride.DockerRepository, + CiBuildConfig: ciTemplateBean.CiBuildConfig, + } + } + for _, material := range pipeline.CiPipelineMaterials { + // ignore those materials which have inactive git material + if material == nil || material.GitMaterial == nil || !material.GitMaterial.Active { + continue + } + ciMaterial := &bean.CiMaterial{ + Id: material.Id, + CheckoutPath: material.CheckoutPath, + Path: material.Path, + ScmId: material.ScmId, + GitMaterialId: material.GitMaterialId, + GitMaterialName: material.GitMaterial.Name[strings.Index(material.GitMaterial.Name, "-")+1:], + ScmName: material.ScmName, + ScmVersion: material.ScmVersion, + IsRegex: material.Regex != "", + Source: &bean.SourceTypeConfig{Type: material.Type, Value: material.Value, Regex: material.Regex}, + } + ciPipeline.CiMaterial = append(ciPipeline.CiMaterial, ciMaterial) + } + linkedCis, err := impl.ciPipelineRepository.FindByParentCiPipelineId(ciPipeline.Id) + if err != nil && !util.IsErrNoRows(err) { + return nil, err + } + ciPipeline.LinkedCount = len(linkedCis) + ciPipelineResp = append(ciPipelineResp, ciPipeline) + } + ciConfig.CiPipelines = ciPipelineResp + ciConfigs = append(ciConfigs, ciConfig) + } + //--------pipeline population end + return ciConfigs, err +} + +func (impl PipelineBuilderImpl) GetCdPipelinesByEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) (cdPipelines *bean.CdPipelines, err error) { + cdPipelines, err = impl.ciCdPipelineOrchestrator.GetCdPipelinesForEnv(envId) + if err != nil { + impl.logger.Errorw("error in fetching pipeline", "err", err) + return cdPipelines, err + } + + //authorization block starts here + var envObjectArr []string + var appObjectArr []string + rbacObjectMap := make(map[int][]string) + for _, dbPipeline := range cdPipelines.Pipelines { + appObject := impl.enforcerUtil.GetAppRBACName(dbPipeline.AppName) + envObject := impl.enforcerUtil.GetEnvRBACNameByCdPipelineIdAndEnvId(dbPipeline.Id) + appObjectArr = append(appObjectArr, appObject) + envObjectArr = append(envObjectArr, envObject) + rbacObjectMap[dbPipeline.Id] = []string{appObject, envObject} + } + //authorization block ends here + appResults, envResults := checkAuthBatch(emailId, appObjectArr, envObjectArr) + var pipelines []*bean.CDPipelineConfigObject + for _, dbPipeline := range cdPipelines.Pipelines { + appObject := rbacObjectMap[dbPipeline.Id][0] + envObject := rbacObjectMap[dbPipeline.Id][1] + if !(appResults[appObject] && envResults[envObject]) { + //if user unauthorized, skip items + continue + } + environment, err := impl.environmentRepository.FindById(dbPipeline.EnvironmentId) + if err != nil && errors.IsNotFound(err) { + impl.logger.Errorw("error in fetching pipeline", "err", err) + return cdPipelines, err + } + strategies, err := impl.pipelineConfigRepository.GetAllStrategyByPipelineId(dbPipeline.Id) + if err != nil && errors.IsNotFound(err) { + impl.logger.Errorw("error in fetching strategies", "err", err) + return cdPipelines, err + } + var strategiesBean []bean.Strategy + var deploymentTemplate chartRepoRepository.DeploymentStrategy + for _, item := range strategies { + strategiesBean = append(strategiesBean, bean.Strategy{ + Config: []byte(item.Config), + DeploymentTemplate: item.Strategy, + Default: item.Default, + }) + if item.Default { + deploymentTemplate = item.Strategy + } + } + appWorkflowMapping, err := impl.appWorkflowRepository.FindWFCDMappingByCDPipelineId(dbPipeline.Id) + if err != nil && errors.IsNotFound(err) { + impl.logger.Errorw("error in fetching workflows", "err", err) + return nil, err + } + pipeline := &bean.CDPipelineConfigObject{ + Id: dbPipeline.Id, + Name: dbPipeline.Name, + EnvironmentId: dbPipeline.EnvironmentId, + EnvironmentName: environment.Name, + CiPipelineId: dbPipeline.CiPipelineId, + DeploymentTemplate: deploymentTemplate, + TriggerType: dbPipeline.TriggerType, + Strategies: strategiesBean, + PreStage: dbPipeline.PreStage, + PostStage: dbPipeline.PostStage, + PreStageConfigMapSecretNames: dbPipeline.PreStageConfigMapSecretNames, + PostStageConfigMapSecretNames: dbPipeline.PostStageConfigMapSecretNames, + RunPreStageInEnv: dbPipeline.RunPreStageInEnv, + RunPostStageInEnv: dbPipeline.RunPostStageInEnv, + DeploymentAppType: dbPipeline.DeploymentAppType, + ParentPipelineType: appWorkflowMapping.ParentType, + ParentPipelineId: appWorkflowMapping.ParentId, + } + pipelines = append(pipelines, pipeline) + } + cdPipelines.Pipelines = pipelines + return cdPipelines, err +} + +func (impl PipelineBuilderImpl) GetExternalCiByEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) (ciConfig []*bean.ExternalCiConfig, err error) { + externalCiConfigs := make([]*bean.ExternalCiConfig, 0) + pipelines, err := impl.pipelineRepository.FindActiveByEnvId(envId) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error fetching pipelines for env id", "err", err) + return nil, err + } + var appIds []int + //authorization block starts here + var appObjectArr []string + rbacObjectMap := make(map[int][]string) + for _, pipeline := range pipelines { + appObject := impl.enforcerUtil.GetAppRBACName(pipeline.App.AppName) + appObjectArr = append(appObjectArr, appObject) + rbacObjectMap[pipeline.Id] = []string{appObject, ""} + } + appResults, _ := checkAuthBatch(emailId, appObjectArr, []string{}) + for _, pipeline := range pipelines { + appObject := rbacObjectMap[pipeline.Id][0] + if !appResults[appObject] { + //if user unauthorized, skip items + continue + } + appIds = append(appIds, pipeline.AppId) + } + //authorization block ends here + if len(appIds) == 0 { + impl.logger.Warnw("there is no app id found for fetching external ci pipelines", "envId", envId) + return externalCiConfigs, nil + } + externalCiPipelines, err := impl.ciPipelineRepository.FindExternalCiByAppIds(appIds) + if err != nil && !util.IsErrNoRows(err) { + impl.logger.Errorw("error in fetching external ci", "envId", envId, "err", err) + return nil, err + } + hostUrl, err := impl.attributesService.GetByKey(attributes.HostUrlKey) + if err != nil { + impl.logger.Errorw("error in fetching external ci", "envId", envId, "err", err) + return nil, err + } + if hostUrl != nil { + impl.ciConfig.ExternalCiWebhookUrl = fmt.Sprintf("%s/%s", hostUrl.Value, ExternalCiWebhookPath) + } + + for _, externalCiPipeline := range externalCiPipelines { + externalCiConfig := &bean.ExternalCiConfig{ + Id: externalCiPipeline.Id, + WebhookUrl: fmt.Sprintf("%s/%d", impl.ciConfig.ExternalCiWebhookUrl, externalCiPipeline.Id), + Payload: impl.ciConfig.ExternalCiPayload, + AccessKey: "", + } + + appWorkflowMappings, err := impl.appWorkflowRepository.FindWFCDMappingByExternalCiId(externalCiPipeline.Id) + if err != nil && !util.IsErrNoRows(err) { + impl.logger.Errorw("error in fetching external ci", "envId", envId, "err", err) + return nil, err + } + + roleData := make(map[string]interface{}) + for _, appWorkflowMapping := range appWorkflowMappings { + cdPipeline, err := impl.pipelineRepository.FindById(appWorkflowMapping.ComponentId) + if err != nil && !util.IsErrNoRows(err) { + impl.logger.Errorw("error in fetching external ci", "envId", envId, "err", err) + return nil, err + } + if _, ok := roleData[teamIdKey]; !ok { + app, err := impl.appRepo.FindAppAndProjectByAppId(cdPipeline.AppId) + if err != nil && !util.IsErrNoRows(err) { + impl.logger.Errorw("error in fetching external ci", "envId", envId, "err", err) + return nil, err + } + roleData[teamIdKey] = app.TeamId + roleData[teamNameKey] = app.Team.Name + roleData[appIdKey] = cdPipeline.AppId + roleData[appNameKey] = cdPipeline.App.AppName + } + if _, ok := roleData[environmentNameKey]; !ok { + roleData[environmentNameKey] = cdPipeline.Environment.Name + } else { + roleData[environmentNameKey] = fmt.Sprintf("%s,%s", roleData[environmentNameKey], cdPipeline.Environment.Name) + } + if _, ok := roleData[environmentIdentifierKey]; !ok { + roleData[environmentIdentifierKey] = cdPipeline.Environment.EnvironmentIdentifier + } else { + roleData[environmentIdentifierKey] = fmt.Sprintf("%s,%s", roleData[environmentIdentifierKey], cdPipeline.Environment.EnvironmentIdentifier) + } + } + + externalCiConfig.ExternalCiConfigRole = bean.ExternalCiConfigRole{ + ProjectId: roleData[teamIdKey].(int), + ProjectName: roleData[teamNameKey].(string), + AppId: roleData[appIdKey].(int), + AppName: roleData[appNameKey].(string), + EnvironmentName: roleData[environmentNameKey].(string), + EnvironmentIdentifier: roleData[environmentIdentifierKey].(string), + Role: "Build and deploy", + } + externalCiConfigs = append(externalCiConfigs, externalCiConfig) + } + //--------pipeline population end + return externalCiConfigs, err +} + +func (impl PipelineBuilderImpl) GetEnvironmentListForAutocompleteFilter(envName string, clusterIds []int, offset int, size int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) (*cluster.AppGroupingResponse, error) { + result := &cluster.AppGroupingResponse{} + var models []*repository2.Environment + var beans []cluster.EnvironmentBean + var err error + if len(envName) > 0 && len(clusterIds) > 0 { + models, err = impl.environmentRepository.FindByEnvNameAndClusterIds(envName, clusterIds) + } else if len(clusterIds) > 0 { + models, err = impl.environmentRepository.FindByClusterIdsWithFilter(clusterIds) + } else if len(envName) > 0 { + models, err = impl.environmentRepository.FindByEnvName(envName) + } else { + models, err = impl.environmentRepository.FindAllActiveWithFilter() + } + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error in fetching environment", "err", err) + return result, err + } + for _, model := range models { + environment := cluster.EnvironmentBean{ + Id: model.Id, + Environment: model.Name, + Namespace: model.Namespace, + CdArgoSetup: model.Cluster.CdArgoSetup, + EnvironmentIdentifier: model.EnvironmentIdentifier, + ClusterName: model.Cluster.ClusterName, + } + pipelines, err := impl.pipelineRepository.FindActiveByEnvId(model.Id) + if err != nil && err != pg.ErrNoRows { + return result, err + } + appCount := 0 + //authorization block starts here + var envObjectArr []string + var appObjectArr []string + rbacObjectMap := make(map[int][]string) + for _, pipeline := range pipelines { + appObject := impl.enforcerUtil.GetAppRBACName(pipeline.App.AppName) + envObject := impl.enforcerUtil.GetEnvRBACNameByCdPipelineIdAndEnvId(pipeline.Id) + appObjectArr = append(appObjectArr, appObject) + envObjectArr = append(envObjectArr, envObject) + rbacObjectMap[pipeline.Id] = []string{appObject, envObject} + } + appResults, envResults := checkAuthBatch(emailId, appObjectArr, envObjectArr) + for _, pipeline := range pipelines { + appObject := rbacObjectMap[pipeline.Id][0] + envObject := rbacObjectMap[pipeline.Id][1] + if !(appResults[appObject] && envResults[envObject]) { + //if user unauthorized, skip items + continue + } + appCount = appCount + 1 + } + //authorization block ends here + environment.AppCount = appCount + beans = append(beans, environment) + } + + envCount := len(beans) + // Apply pagination + if size > 0 { + if offset+size <= len(beans) { + beans = beans[offset : offset+size] + } else { + beans = beans[offset:] + } + } + result.EnvList = beans + result.EnvCount = envCount + return result, nil +} + +func (impl PipelineBuilderImpl) GetAppListForEnvironment(envId int, emailId string, checkAuthBatch func(emailId string, appObject []string, envObject []string) (map[string]bool, map[string]bool)) ([]*AppBean, error) { + var appsRes []*AppBean + pipelines, err := impl.pipelineRepository.FindActiveByEnvId(envId) + if err != nil { + impl.logger.Errorw("error while fetching app", "err", err) + return nil, err + } + + //authorization block starts here + var envObjectArr []string + var appObjectArr []string + rbacObjectMap := make(map[int][]string) + for _, pipeline := range pipelines { + appObject := impl.enforcerUtil.GetAppRBACName(pipeline.App.AppName) + envObject := impl.enforcerUtil.GetEnvRBACNameByCdPipelineIdAndEnvId(pipeline.Id) + appObjectArr = append(appObjectArr, appObject) + envObjectArr = append(envObjectArr, envObject) + rbacObjectMap[pipeline.Id] = []string{appObject, envObject} + } + appResults, envResults := checkAuthBatch(emailId, appObjectArr, envObjectArr) + for _, pipeline := range pipelines { + appObject := rbacObjectMap[pipeline.Id][0] + envObject := rbacObjectMap[pipeline.Id][1] + if !(appResults[appObject] && envResults[envObject]) { + //if user unauthorized, skip items + continue + } + appsRes = append(appsRes, &AppBean{Id: pipeline.AppId, Name: pipeline.App.AppName}) + } + //authorization block ends here + return appsRes, err +} diff --git a/util/rbac/EnforcerUtil.go b/util/rbac/EnforcerUtil.go index 6bff4e8e05..c996b29819 100644 --- a/util/rbac/EnforcerUtil.go +++ b/util/rbac/EnforcerUtil.go @@ -47,7 +47,7 @@ type EnforcerUtil interface { GetHelmObject(appId int, envId int) (string, string) GetHelmObjectByAppNameAndEnvId(appName string, envId int) (string, string) GetHelmObjectByProjectIdAndEnvId(teamId int, envId int) (string, string) - GetEnvRBACNameByCdPipelineIdAndEnvId(cdPipelineId int, envId int) string + GetEnvRBACNameByCdPipelineIdAndEnvId(cdPipelineId int) string GetAppRBACNameByTeamIdAndAppId(teamId int, appId int) string GetRBACNameForClusterEntity(clusterName string, resourceIdentifier application.ResourceIdentifier) (resourceName, objectName string) } @@ -211,22 +211,13 @@ func (impl EnforcerUtilImpl) GetEnvRBACNameByCiPipelineIdAndEnvId(ciPipelineId i return fmt.Sprintf("%s/%s", strings.ToLower(env.EnvironmentIdentifier), strings.ToLower(appName)) } -func (impl EnforcerUtilImpl) GetEnvRBACNameByCdPipelineIdAndEnvId(cdPipelineId int, envId int) string { +func (impl EnforcerUtilImpl) GetEnvRBACNameByCdPipelineIdAndEnvId(cdPipelineId int) string { pipeline, err := impl.pipelineRepository.FindById(cdPipelineId) if err != nil { impl.logger.Error(err) return fmt.Sprintf("%s/%s", "", "") } - application, err := impl.appRepo.FindById(pipeline.AppId) - if err != nil { - return fmt.Sprintf("%s/%s", "", "") - } - appName := application.AppName - env, err := impl.environmentRepository.FindById(envId) - if err != nil { - return fmt.Sprintf("%s/%s", "", strings.ToLower(appName)) - } - return fmt.Sprintf("%s/%s", strings.ToLower(env.EnvironmentIdentifier), strings.ToLower(appName)) + return fmt.Sprintf("%s/%s", strings.ToLower(pipeline.Environment.EnvironmentIdentifier), strings.ToLower(pipeline.App.AppName)) } func (impl EnforcerUtilImpl) GetTeamRbacObjectByCiPipelineId(ciPipelineId int) string { diff --git a/wire_gen.go b/wire_gen.go index 1167298d1a..8d1e090087 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -399,23 +399,23 @@ func InitializeApp() (*App, error) { if err != nil { return nil, err } - pipelineBuilderImpl := pipeline.NewPipelineBuilderImpl(sugaredLogger, ciCdPipelineOrchestratorImpl, dockerArtifactStoreRepositoryImpl, materialRepositoryImpl, appRepositoryImpl, pipelineRepositoryImpl, propertiesConfigServiceImpl, ciTemplateRepositoryImpl, ciPipelineRepositoryImpl, applicationServiceClientImpl, chartRepositoryImpl, ciArtifactRepositoryImpl, ecrConfig, envConfigOverrideRepositoryImpl, environmentRepositoryImpl, pipelineConfigRepositoryImpl, utilMergeUtil, appWorkflowRepositoryImpl, ciConfig, cdWorkflowRepositoryImpl, appServiceImpl, imageScanResultRepositoryImpl, argoK8sClientImpl, gitFactory, attributesServiceImpl, acdAuthConfig, gitOpsConfigRepositoryImpl, pipelineStrategyHistoryServiceImpl, prePostCiScriptHistoryServiceImpl, prePostCdScriptHistoryServiceImpl, deploymentTemplateHistoryServiceImpl, appLevelMetricsRepositoryImpl, pipelineStageServiceImpl, chartRefRepositoryImpl, chartTemplateServiceImpl, chartServiceImpl, helmAppServiceImpl, deploymentGroupRepositoryImpl, ciPipelineMaterialRepositoryImpl, userServiceImpl, ciTemplateServiceImpl, ciTemplateOverrideRepositoryImpl, gitMaterialHistoryServiceImpl, ciTemplateHistoryServiceImpl, ciPipelineHistoryServiceImpl, globalStrategyMetadataRepositoryImpl, globalStrategyMetadataChartRefMappingRepositoryImpl, deploymentServiceTypeConfig, appStatusRepositoryImpl) + pipelineBuilderImpl := pipeline.NewPipelineBuilderImpl(sugaredLogger, ciCdPipelineOrchestratorImpl, dockerArtifactStoreRepositoryImpl, materialRepositoryImpl, appRepositoryImpl, pipelineRepositoryImpl, propertiesConfigServiceImpl, ciTemplateRepositoryImpl, ciPipelineRepositoryImpl, applicationServiceClientImpl, chartRepositoryImpl, ciArtifactRepositoryImpl, ecrConfig, envConfigOverrideRepositoryImpl, environmentRepositoryImpl, pipelineConfigRepositoryImpl, utilMergeUtil, appWorkflowRepositoryImpl, ciConfig, cdWorkflowRepositoryImpl, appServiceImpl, imageScanResultRepositoryImpl, argoK8sClientImpl, gitFactory, attributesServiceImpl, acdAuthConfig, gitOpsConfigRepositoryImpl, pipelineStrategyHistoryServiceImpl, prePostCiScriptHistoryServiceImpl, prePostCdScriptHistoryServiceImpl, deploymentTemplateHistoryServiceImpl, appLevelMetricsRepositoryImpl, pipelineStageServiceImpl, chartRefRepositoryImpl, chartTemplateServiceImpl, chartServiceImpl, helmAppServiceImpl, deploymentGroupRepositoryImpl, ciPipelineMaterialRepositoryImpl, userServiceImpl, ciTemplateServiceImpl, ciTemplateOverrideRepositoryImpl, gitMaterialHistoryServiceImpl, ciTemplateHistoryServiceImpl, ciPipelineHistoryServiceImpl, globalStrategyMetadataRepositoryImpl, globalStrategyMetadataChartRefMappingRepositoryImpl, deploymentServiceTypeConfig, appStatusRepositoryImpl, enforcerUtilImpl) dbMigrationServiceImpl := pipeline.NewDbMogrationService(sugaredLogger, dbMigrationConfigRepositoryImpl) globalCMCSRepositoryImpl := repository.NewGlobalCMCSRepositoryImpl(sugaredLogger, db) globalCMCSServiceImpl := pipeline.NewGlobalCMCSServiceImpl(sugaredLogger, globalCMCSRepositoryImpl) workflowServiceImpl := pipeline.NewWorkflowServiceImpl(sugaredLogger, ciConfig, globalCMCSServiceImpl) ciServiceImpl := pipeline.NewCiServiceImpl(sugaredLogger, workflowServiceImpl, ciPipelineMaterialRepositoryImpl, ciWorkflowRepositoryImpl, ciConfig, eventRESTClientImpl, eventSimpleFactoryImpl, mergeUtil, ciPipelineRepositoryImpl, prePostCiScriptHistoryServiceImpl, pipelineStageServiceImpl, userServiceImpl, ciTemplateServiceImpl, appCrudOperationServiceImpl) ciLogServiceImpl := pipeline.NewCiLogServiceImpl(sugaredLogger, ciServiceImpl, ciConfig) - ciHandlerImpl := pipeline.NewCiHandlerImpl(sugaredLogger, ciServiceImpl, ciPipelineMaterialRepositoryImpl, gitSensorClientImpl, ciWorkflowRepositoryImpl, workflowServiceImpl, ciLogServiceImpl, ciConfig, ciArtifactRepositoryImpl, userServiceImpl, eventRESTClientImpl, eventSimpleFactoryImpl, ciPipelineRepositoryImpl, appListingRepositoryImpl, k8sUtil) + ciHandlerImpl := pipeline.NewCiHandlerImpl(sugaredLogger, ciServiceImpl, ciPipelineMaterialRepositoryImpl, gitSensorClientImpl, ciWorkflowRepositoryImpl, workflowServiceImpl, ciLogServiceImpl, ciConfig, ciArtifactRepositoryImpl, userServiceImpl, eventRESTClientImpl, eventSimpleFactoryImpl, ciPipelineRepositoryImpl, appListingRepositoryImpl, k8sUtil, pipelineRepositoryImpl, enforcerUtilImpl) gitRegistryConfigImpl := pipeline.NewGitRegistryConfigImpl(sugaredLogger, gitProviderRepositoryImpl, gitSensorClientImpl) dockerRegistryConfigImpl := pipeline.NewDockerRegistryConfigImpl(sugaredLogger, dockerArtifactStoreRepositoryImpl, dockerRegistryIpsConfigRepositoryImpl) appListingViewBuilderImpl := app2.NewAppListingViewBuilderImpl(sugaredLogger) linkoutsRepositoryImpl := repository.NewLinkoutsRepositoryImpl(sugaredLogger, db) appListingServiceImpl := app2.NewAppListingServiceImpl(sugaredLogger, appListingRepositoryImpl, applicationServiceClientImpl, appRepositoryImpl, appListingViewBuilderImpl, pipelineRepositoryImpl, linkoutsRepositoryImpl, appLevelMetricsRepositoryImpl, envLevelAppMetricsRepositoryImpl, cdWorkflowRepositoryImpl, pipelineOverrideRepositoryImpl, environmentRepositoryImpl, argoUserServiceImpl, envConfigOverrideRepositoryImpl, chartRepositoryImpl, ciPipelineRepositoryImpl, dockerRegistryIpsConfigServiceImpl) deploymentEventHandlerImpl := app2.NewDeploymentEventHandlerImpl(sugaredLogger, appListingServiceImpl, eventRESTClientImpl, eventSimpleFactoryImpl) - cdHandlerImpl := pipeline.NewCdHandlerImpl(sugaredLogger, cdConfig, userServiceImpl, cdWorkflowRepositoryImpl, cdWorkflowServiceImpl, ciLogServiceImpl, ciArtifactRepositoryImpl, ciPipelineMaterialRepositoryImpl, pipelineRepositoryImpl, environmentRepositoryImpl, ciWorkflowRepositoryImpl, ciConfig, helmAppServiceImpl, pipelineOverrideRepositoryImpl, workflowDagExecutorImpl, appListingServiceImpl, appListingRepositoryImpl, pipelineStatusTimelineRepositoryImpl, applicationServiceClientImpl, argoUserServiceImpl, deploymentEventHandlerImpl, eventRESTClientImpl, pipelineStatusTimelineResourcesServiceImpl, pipelineStatusSyncDetailServiceImpl, pipelineStatusTimelineServiceImpl, appServiceImpl, appStatusServiceImpl) + cdHandlerImpl := pipeline.NewCdHandlerImpl(sugaredLogger, cdConfig, userServiceImpl, cdWorkflowRepositoryImpl, cdWorkflowServiceImpl, ciLogServiceImpl, ciArtifactRepositoryImpl, ciPipelineMaterialRepositoryImpl, pipelineRepositoryImpl, environmentRepositoryImpl, ciWorkflowRepositoryImpl, ciConfig, helmAppServiceImpl, pipelineOverrideRepositoryImpl, workflowDagExecutorImpl, appListingServiceImpl, appListingRepositoryImpl, pipelineStatusTimelineRepositoryImpl, applicationServiceClientImpl, argoUserServiceImpl, deploymentEventHandlerImpl, eventRESTClientImpl, pipelineStatusTimelineResourcesServiceImpl, pipelineStatusSyncDetailServiceImpl, pipelineStatusTimelineServiceImpl, appServiceImpl, appStatusServiceImpl, enforcerUtilImpl) configMapServiceImpl := pipeline.NewConfigMapServiceImpl(chartRepositoryImpl, sugaredLogger, chartRepoRepositoryImpl, utilMergeUtil, pipelineConfigRepositoryImpl, configMapRepositoryImpl, envConfigOverrideRepositoryImpl, commonServiceImpl, appRepositoryImpl, configMapHistoryServiceImpl) - appWorkflowServiceImpl := appWorkflow2.NewAppWorkflowServiceImpl(sugaredLogger, appWorkflowRepositoryImpl, ciCdPipelineOrchestratorImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl) + appWorkflowServiceImpl := appWorkflow2.NewAppWorkflowServiceImpl(sugaredLogger, appWorkflowRepositoryImpl, ciCdPipelineOrchestratorImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl, enforcerUtilImpl) appCloneServiceImpl := appClone.NewAppCloneServiceImpl(sugaredLogger, pipelineBuilderImpl, materialRepositoryImpl, chartServiceImpl, configMapServiceImpl, appWorkflowServiceImpl, appListingServiceImpl, propertiesConfigServiceImpl, ciTemplateOverrideRepositoryImpl, pipelineStageServiceImpl, ciTemplateServiceImpl) imageScanObjectMetaRepositoryImpl := security.NewImageScanObjectMetaRepositoryImpl(db, sugaredLogger) cveStoreRepositoryImpl := security.NewCveStoreRepositoryImpl(db, sugaredLogger) @@ -682,7 +682,8 @@ func InitializeApp() (*App, error) { return nil, err } ciStatusUpdateCronImpl := cron.NewCiStatusUpdateCronImpl(sugaredLogger, appServiceImpl, ciWorkflowStatusUpdateConfig, ciPipelineRepositoryImpl, ciHandlerImpl) - muxRouter := router.NewMuxRouter(sugaredLogger, pipelineTriggerRouterImpl, pipelineConfigRouterImpl, migrateDbRouterImpl, appListingRouterImpl, environmentRouterImpl, clusterRouterImpl, webhookRouterImpl, userAuthRouterImpl, applicationRouterImpl, cdRouterImpl, projectManagementRouterImpl, gitProviderRouterImpl, gitHostRouterImpl, dockerRegRouterImpl, notificationRouterImpl, teamRouterImpl, gitWebhookHandlerImpl, workflowStatusUpdateHandlerImpl, applicationStatusUpdateHandlerImpl, ciEventHandlerImpl, pubSubClientServiceImpl, userRouterImpl, chartRefRouterImpl, configMapRouterImpl, appStoreRouterImpl, chartRepositoryRouterImpl, releaseMetricsRouterImpl, deploymentGroupRouterImpl, batchOperationRouterImpl, chartGroupRouterImpl, testSuitRouterImpl, imageScanRouterImpl, policyRouterImpl, gitOpsConfigRouterImpl, dashboardRouterImpl, attributesRouterImpl, userAttributesRouterImpl, commonRouterImpl, grafanaRouterImpl, ssoLoginRouterImpl, telemetryRouterImpl, telemetryEventClientImplExtended, bulkUpdateRouterImpl, webhookListenerRouterImpl, appRouterImpl, coreAppRouterImpl, helmAppRouterImpl, k8sApplicationRouterImpl, pProfRouterImpl, deploymentConfigRouterImpl, dashboardTelemetryRouterImpl, commonDeploymentRouterImpl, externalLinkRouterImpl, globalPluginRouterImpl, moduleRouterImpl, serverRouterImpl, apiTokenRouterImpl, cdApplicationStatusUpdateHandlerImpl, k8sCapacityRouterImpl, webhookHelmRouterImpl, globalCMCSRouterImpl, userTerminalAccessRouterImpl, ciStatusUpdateCronImpl) + appGroupingRouterImpl := router.NewAppGroupingRouterImpl(pipelineConfigRestHandlerImpl, appWorkflowRestHandlerImpl) + muxRouter := router.NewMuxRouter(sugaredLogger, pipelineTriggerRouterImpl, pipelineConfigRouterImpl, migrateDbRouterImpl, appListingRouterImpl, environmentRouterImpl, clusterRouterImpl, webhookRouterImpl, userAuthRouterImpl, applicationRouterImpl, cdRouterImpl, projectManagementRouterImpl, gitProviderRouterImpl, gitHostRouterImpl, dockerRegRouterImpl, notificationRouterImpl, teamRouterImpl, gitWebhookHandlerImpl, workflowStatusUpdateHandlerImpl, applicationStatusUpdateHandlerImpl, ciEventHandlerImpl, pubSubClientServiceImpl, userRouterImpl, chartRefRouterImpl, configMapRouterImpl, appStoreRouterImpl, chartRepositoryRouterImpl, releaseMetricsRouterImpl, deploymentGroupRouterImpl, batchOperationRouterImpl, chartGroupRouterImpl, testSuitRouterImpl, imageScanRouterImpl, policyRouterImpl, gitOpsConfigRouterImpl, dashboardRouterImpl, attributesRouterImpl, userAttributesRouterImpl, commonRouterImpl, grafanaRouterImpl, ssoLoginRouterImpl, telemetryRouterImpl, telemetryEventClientImplExtended, bulkUpdateRouterImpl, webhookListenerRouterImpl, appRouterImpl, coreAppRouterImpl, helmAppRouterImpl, k8sApplicationRouterImpl, pProfRouterImpl, deploymentConfigRouterImpl, dashboardTelemetryRouterImpl, commonDeploymentRouterImpl, externalLinkRouterImpl, globalPluginRouterImpl, moduleRouterImpl, serverRouterImpl, apiTokenRouterImpl, cdApplicationStatusUpdateHandlerImpl, k8sCapacityRouterImpl, webhookHelmRouterImpl, globalCMCSRouterImpl, userTerminalAccessRouterImpl, ciStatusUpdateCronImpl, appGroupingRouterImpl) mainApp := NewApp(muxRouter, sugaredLogger, sseSSE, syncedEnforcer, db, pubSubClientServiceImpl, sessionManager, posthogClient) return mainApp, nil }