Skip to content

Commit 6eef41a

Browse files
authored
Merge pull request #243 from devtron-labs/fix-acd-app-delete-non-cascade-sync
fix: acd app delete non cascade sync
2 parents a317c2f + dbab771 commit 6eef41a

27 files changed

+612
-151
lines changed

api/appStore/AppStoreRouter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ func (router AppStoreRouterImpl) Init(configRouter *mux.Router) {
7676
configRouter.Path("/installed-app/detail").Queries("installed-app-id", "{installed-app-id}").Queries("env-id", "{env-id}").
7777
HandlerFunc(router.deployRestHandler.FetchAppDetailsForInstalledApp).
7878
Methods("GET")
79+
configRouter.Path("/installed-app/delete/{installedAppId}/non-cascade").
80+
HandlerFunc(router.deployRestHandler.DeleteArgoInstalledAppWithNonCascade).
81+
Methods("DELETE")
7982
configRouter.Path("/installed-app/detail/v2").Queries("installed-app-id", "{installed-app-id}").Queries("env-id", "{env-id}").
8083
HandlerFunc(router.deployRestHandler.FetchAppDetailsForInstalledAppV2).
8184
Methods("GET")

api/appStore/InstalledAppRestHandler.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package appStore
1919

2020
import (
21+
"context"
2122
"encoding/json"
2223
"errors"
2324
"fmt"
@@ -35,6 +36,7 @@ import (
3536
"github.com/devtron-labs/devtron/pkg/cluster"
3637
"github.com/devtron-labs/devtron/pkg/user"
3738
"github.com/devtron-labs/devtron/pkg/user/casbin"
39+
"github.com/devtron-labs/devtron/util"
3840
"github.com/devtron-labs/devtron/util/argo"
3941
"github.com/devtron-labs/devtron/util/rbac"
4042
"github.com/devtron-labs/devtron/util/response"
@@ -54,6 +56,7 @@ type InstalledAppRestHandler interface {
5456
CheckAppExists(w http.ResponseWriter, r *http.Request)
5557
DefaultComponentInstallation(w http.ResponseWriter, r *http.Request)
5658
FetchAppDetailsForInstalledApp(w http.ResponseWriter, r *http.Request)
59+
DeleteArgoInstalledAppWithNonCascade(w http.ResponseWriter, r *http.Request)
5760
FetchAppDetailsForInstalledAppV2(w http.ResponseWriter, r *http.Request)
5861
FetchResourceTree(w http.ResponseWriter, r *http.Request)
5962
FetchResourceTreeForACDApp(w http.ResponseWriter, r *http.Request)
@@ -67,6 +70,7 @@ type InstalledAppRestHandlerImpl struct {
6770
userAuthService user.UserService
6871
enforcer casbin.Enforcer
6972
enforcerUtil rbac.EnforcerUtil
73+
enforcerUtilHelm rbac.EnforcerUtilHelm
7074
installedAppService service.InstalledAppService
7175
validator *validator.Validate
7276
clusterService cluster.ClusterService
@@ -80,7 +84,7 @@ type InstalledAppRestHandlerImpl struct {
8084
}
8185

8286
func NewInstalledAppRestHandlerImpl(Logger *zap.SugaredLogger, userAuthService user.UserService,
83-
enforcer casbin.Enforcer, enforcerUtil rbac.EnforcerUtil, installedAppService service.InstalledAppService,
87+
enforcer casbin.Enforcer, enforcerUtil rbac.EnforcerUtil, enforcerUtilHelm rbac.EnforcerUtilHelm, installedAppService service.InstalledAppService,
8488
validator *validator.Validate, clusterService cluster.ClusterService, acdServiceClient application.ServiceClient,
8589
appStoreDeploymentService service.AppStoreDeploymentService, helmAppClient client.HelmAppClient, helmAppService client.HelmAppService,
8690
argoUserService argo.ArgoUserService,
@@ -92,6 +96,7 @@ func NewInstalledAppRestHandlerImpl(Logger *zap.SugaredLogger, userAuthService u
9296
userAuthService: userAuthService,
9397
enforcer: enforcer,
9498
enforcerUtil: enforcerUtil,
99+
enforcerUtilHelm: enforcerUtilHelm,
95100
installedAppService: installedAppService,
96101
validator: validator,
97102
clusterService: clusterService,
@@ -430,6 +435,82 @@ func (handler *InstalledAppRestHandlerImpl) FetchNotesForArgoInstalledApp(w http
430435
common.WriteJsonResp(w, err, &bean2.Notes{Notes: notes}, http.StatusOK)
431436

432437
}
438+
439+
func (handler *InstalledAppRestHandlerImpl) DeleteArgoInstalledAppWithNonCascade(w http.ResponseWriter, r *http.Request) {
440+
token := r.Header.Get("token")
441+
userId, err := handler.userAuthService.GetLoggedInUser(r)
442+
if userId == 0 || err != nil {
443+
common.WriteJsonResp(w, err, nil, http.StatusUnauthorized)
444+
return
445+
}
446+
vars := mux.Vars(r)
447+
installedAppId, err := strconv.Atoi(vars["installedAppId"])
448+
if err != nil {
449+
handler.Logger.Errorw("request err, DeleteArgoInstalledAppWithNonCascade", "err", err, "installedAppId", installedAppId)
450+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
451+
return
452+
}
453+
handler.Logger.Infow("request payload, delete app", "appId", installedAppId)
454+
v := r.URL.Query()
455+
forceDelete := false
456+
force := v.Get("force")
457+
if len(force) > 0 {
458+
forceDelete, err = strconv.ParseBool(force)
459+
if err != nil {
460+
handler.Logger.Errorw("request err, NonCascadeDeleteCdPipeline", "err", err)
461+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
462+
return
463+
}
464+
}
465+
installedApp, err := handler.appStoreDeploymentService.GetInstalledApp(installedAppId)
466+
if err != nil {
467+
handler.Logger.Error("request err, NonCascadeDeleteCdPipeline", "err", err)
468+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
469+
return
470+
}
471+
if util.IsBaseStack() || util.IsHelmApp(installedApp.AppOfferingMode) || util2.IsHelmApp(installedApp.DeploymentAppType) {
472+
handler.Logger.Errorw("request err, NonCascadeDeleteCdPipeline", "err", fmt.Errorf("nocascade delete is not supported for %s", installedApp.AppName))
473+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
474+
return
475+
}
476+
//rbac block starts from here
477+
rbacObject, rbacObject2 := handler.enforcerUtil.GetHelmObjectByAppNameAndEnvId(installedApp.AppName, installedApp.EnvironmentId)
478+
ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionDelete, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionDelete, rbacObject2)
479+
if !ok {
480+
common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), nil, http.StatusForbidden)
481+
return
482+
}
483+
//rback block ends here
484+
acdToken, err := handler.argoUserService.GetLatestDevtronArgoCdUserToken()
485+
if err != nil {
486+
handler.Logger.Errorw("error in getting acd token", "err", err)
487+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
488+
return
489+
}
490+
ctx := context.WithValue(r.Context(), "token", acdToken)
491+
request := &appStoreBean.InstallAppVersionDTO{}
492+
request.InstalledAppId = installedAppId
493+
request.AppName = installedApp.AppName
494+
request.AppId = installedApp.AppId
495+
request.EnvironmentId = installedApp.EnvironmentId
496+
request.UserId = userId
497+
request.ForceDelete = forceDelete
498+
request.NonCascadeDelete = true
499+
request.AppOfferingMode = installedApp.AppOfferingMode
500+
request.ClusterId = installedApp.ClusterId
501+
request.Namespace = installedApp.Namespace
502+
request.AcdPartialDelete = true
503+
504+
request, err = handler.appStoreDeploymentService.DeleteInstalledApp(ctx, request)
505+
if err != nil {
506+
handler.Logger.Errorw("service err, DeleteInstalledApp", "err", err, "installAppId", installedAppId)
507+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
508+
return
509+
}
510+
common.WriteJsonResp(w, nil, nil, http.StatusOK)
511+
512+
}
513+
433514
func (handler *InstalledAppRestHandlerImpl) checkNotesAuth(token string, appName string, envId int) bool {
434515

435516
object, object2 := handler.enforcerUtil.GetHelmObjectByAppNameAndEnvId(appName, envId)

api/appStore/deployment/AppStoreDeploymentRestHandler.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,25 @@ func (handler AppStoreDeploymentRestHandlerImpl) DeleteInstalledApp(w http.Respo
256256
v := r.URL.Query()
257257
forceDelete := false
258258
force := v.Get("force")
259+
cascadeDelete := true
260+
cascade := v.Get("cascade")
261+
if len(force) > 0 && len(cascade) > 0 {
262+
handler.Logger.Errorw("request err, PatchCdPipeline", "err", fmt.Errorf("cannot perform both cascade and force delete"), "installAppId", installAppId)
263+
common.WriteJsonResp(w, fmt.Errorf("invalid query params! cannot perform both force and cascade together"), nil, http.StatusBadRequest)
264+
return
265+
}
259266
if len(force) > 0 {
260267
forceDelete, err = strconv.ParseBool(force)
261268
if err != nil {
262269
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
263270
return
264271
}
272+
} else if len(cascade) > 0 {
273+
cascadeDelete, err = strconv.ParseBool(cascade)
274+
if err != nil {
275+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
276+
return
277+
}
265278
}
266279
partialDelete := false
267280
partialDeleteStr := v.Get("partialDelete")
@@ -311,6 +324,7 @@ func (handler AppStoreDeploymentRestHandlerImpl) DeleteInstalledApp(w http.Respo
311324
request.EnvironmentId = installedApp.EnvironmentId
312325
request.UserId = userId
313326
request.ForceDelete = forceDelete
327+
request.NonCascadeDelete = !cascadeDelete
314328
request.AppOfferingMode = installedApp.AppOfferingMode
315329
request.ClusterId = installedApp.ClusterId
316330
request.Namespace = installedApp.Namespace

api/cluster/EnvironmentRestHandler.go

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
package cluster
1919

2020
import (
21+
"context"
2122
"encoding/json"
23+
"github.com/devtron-labs/devtron/internal/util"
24+
"github.com/devtron-labs/devtron/util/k8s"
25+
"k8s.io/client-go/kubernetes"
2226
"net/http"
2327
"strconv"
2428
"strings"
@@ -51,12 +55,14 @@ type EnvironmentRestHandler interface {
5155
FindById(w http.ResponseWriter, r *http.Request)
5256
GetEnvironmentListForAutocomplete(w http.ResponseWriter, r *http.Request)
5357
GetCombinedEnvironmentListForDropDown(w http.ResponseWriter, r *http.Request)
58+
GetEnvironmentConnection(w http.ResponseWriter, r *http.Request)
5459
DeleteEnvironment(w http.ResponseWriter, r *http.Request)
5560
GetCombinedEnvironmentListForDropDownByClusterIds(w http.ResponseWriter, r *http.Request)
5661
}
5762

5863
type EnvironmentRestHandlerImpl struct {
5964
environmentClusterMappingsService request.EnvironmentService
65+
k8sApplicationService k8s.K8sApplicationService
6066
logger *zap.SugaredLogger
6167
userService user.UserService
6268
validator *validator.Validate
@@ -65,7 +71,12 @@ type EnvironmentRestHandlerImpl struct {
6571
cfg *bean.Config
6672
}
6773

68-
func NewEnvironmentRestHandlerImpl(svc request.EnvironmentService, logger *zap.SugaredLogger, userService user.UserService,
74+
type ClusterReachableResponse struct {
75+
ClusterReachable bool `json:"clusterReachable"`
76+
ClusterName string `json:"clusterName"`
77+
}
78+
79+
func NewEnvironmentRestHandlerImpl(svc request.EnvironmentService, k8sApplicationService k8s.K8sApplicationService, logger *zap.SugaredLogger, userService user.UserService,
6980
validator *validator.Validate, enforcer casbin.Enforcer,
7081
deleteService delete2.DeleteService,
7182
) *EnvironmentRestHandlerImpl {
@@ -78,6 +89,7 @@ func NewEnvironmentRestHandlerImpl(svc request.EnvironmentService, logger *zap.S
7889
logger.Infow("evironment rest handler initialized", "ignoreAuthCheckValue", cfg.IgnoreAuthCheck)
7990
return &EnvironmentRestHandlerImpl{
8091
environmentClusterMappingsService: svc,
92+
k8sApplicationService: k8sApplicationService,
8193
logger: logger,
8294
userService: userService,
8395
validator: validator,
@@ -547,3 +559,73 @@ func (impl EnvironmentRestHandlerImpl) GetCombinedEnvironmentListForDropDownByCl
547559
}
548560
common.WriteJsonResp(w, err, clusters, http.StatusOK)
549561
}
562+
563+
func (impl EnvironmentRestHandlerImpl) GetEnvironmentConnection(w http.ResponseWriter, r *http.Request) {
564+
//token := r.Header.Get("token")
565+
vars := mux.Vars(r)
566+
envIdString := vars["envId"]
567+
envId, err := strconv.Atoi(envIdString)
568+
if err != nil {
569+
impl.logger.Errorw("failed to extract clusterId from param", "error", err, "clusterId", envIdString)
570+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
571+
return
572+
}
573+
userId, err := impl.userService.GetLoggedInUser(r)
574+
if userId == 0 || err != nil {
575+
impl.logger.Errorw("user not authorized", "error", err, "userId", userId)
576+
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
577+
return
578+
}
579+
bean, err := impl.environmentClusterMappingsService.FindById(envId)
580+
if err != nil {
581+
impl.logger.Errorw("request err, FindById", "err", err, "envId", envId)
582+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
583+
return
584+
}
585+
clusterBean, err := impl.environmentClusterMappingsService.FindClusterByEnvId(bean.ClusterId)
586+
if err != nil {
587+
impl.logger.Errorw("request err, FindById", "err", err, "envId", envId)
588+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
589+
return
590+
}
591+
// RBAC enforcer applying
592+
token := r.Header.Get("token")
593+
if ok := impl.enforcer.Enforce(token, casbin.ResourceGlobalEnvironment, casbin.ActionGet, strings.ToLower(bean.EnvironmentIdentifier)); !ok {
594+
common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
595+
return
596+
}
597+
//RBAC enforcer Ends
598+
// getting restConfig and clientSet outside the goroutine because we don't want to call goroutine func with receiver function
599+
restConfig, err := impl.k8sApplicationService.GetRestConfigByClusterId(context.Background(), clusterBean.Id)
600+
if err != nil {
601+
impl.logger.Errorw("error in getting restConfig by cluster", "err", err, "clusterId", clusterBean.Id)
602+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
603+
return
604+
}
605+
k8sHttpClient, err := util.OverrideK8sHttpClientWithTracer(restConfig)
606+
if err != nil {
607+
impl.logger.Errorw("service err, OverrideK8sHttpClientWithTracer", "err", err, "restConfig", restConfig)
608+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
609+
return
610+
}
611+
k8sClientSet, err := kubernetes.NewForConfigAndClient(restConfig, k8sHttpClient)
612+
if err != nil {
613+
impl.logger.Errorw("error in getting client set by rest config", "err", err, "restConfig", restConfig)
614+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
615+
return
616+
}
617+
responseObj := &ClusterReachableResponse{
618+
ClusterReachable: true,
619+
ClusterName: clusterBean.ClusterName,
620+
}
621+
err = impl.k8sApplicationService.FetchConnectionStatusForCluster(k8sClientSet, clusterBean.Id)
622+
if err != nil {
623+
responseObj.ClusterReachable = false
624+
}
625+
//updating the cluster connection error to db
626+
mapObj := map[int]error{
627+
clusterBean.Id: err,
628+
}
629+
impl.environmentClusterMappingsService.HandleErrorInClusterConnections([]*request.ClusterBean{clusterBean}, mapObj, true)
630+
common.WriteJsonResp(w, nil, responseObj, http.StatusOK)
631+
}

api/cluster/EnvironmentRouter.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,7 @@ func (impl EnvironmentRouterImpl) InitEnvironmentClusterMappingsRouter(environme
7676
environmentClusterMappingsRouter.Path("/namespace/autocomplete").
7777
Methods("GET").
7878
HandlerFunc(impl.environmentClusterMappingsRestHandler.GetCombinedEnvironmentListForDropDownByClusterIds)
79-
79+
environmentClusterMappingsRouter.Path("/{envId}/connection").
80+
Methods("GET").
81+
HandlerFunc(impl.environmentClusterMappingsRestHandler.GetEnvironmentConnection)
8082
}

api/restHandler/BulkUpdateRestHandler.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -422,19 +422,11 @@ func (handler BulkUpdateRestHandlerImpl) HandleCdPipelineBulkAction(w http.Respo
422422
return
423423
}
424424

425-
v := r.URL.Query()
426-
forceDelete := false
427-
forceDeleteParam := v.Get("forceDelete")
428-
if len(forceDeleteParam) > 0 {
429-
forceDelete, err = strconv.ParseBool(forceDeleteParam)
430-
if err != nil {
431-
handler.logger.Errorw("request err, HandleCdPipelineBulkAction", "err", err, "payload", cdPipelineBulkActionReq)
432-
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
433-
return
434-
}
425+
if cdPipelineBulkActionReq.ForceDelete {
426+
cdPipelineBulkActionReq.NonCascadeDelete = true
435427
}
436-
cdPipelineBulkActionReq.ForceDelete = forceDelete
437428

429+
v := r.URL.Query()
438430
dryRun := false
439431
dryRunParam := v.Get("dryRun")
440432
if len(dryRunParam) > 0 {

api/restHandler/CoreAppRestHandler.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,11 +1147,12 @@ func (handler CoreAppRestHandlerImpl) deleteApp(ctx context.Context, appId int,
11471147

11481148
for _, cdPipeline := range cdPipelines.Pipelines {
11491149
cdPipelineDeleteRequest := &bean.CDPatchRequest{
1150-
AppId: appId,
1151-
UserId: userId,
1152-
Action: bean.CD_DELETE,
1153-
ForceDelete: true,
1154-
Pipeline: cdPipeline,
1150+
AppId: appId,
1151+
UserId: userId,
1152+
Action: bean.CD_DELETE,
1153+
ForceDelete: true,
1154+
NonCascadeDelete: false,
1155+
Pipeline: cdPipeline,
11551156
}
11561157
_, err = handler.pipelineBuilder.PatchCdPipelines(cdPipelineDeleteRequest, ctx)
11571158
if err != nil {

api/restHandler/app/DeploymentPipelineRestHandler.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,16 +238,31 @@ func (handler PipelineConfigRestHandlerImpl) PatchCdPipeline(w http.ResponseWrit
238238

239239
v := r.URL.Query()
240240
forceDelete := false
241+
cascadeDelete := true
241242
force := v.Get("force")
243+
cascade := v.Get("cascade")
244+
if len(force) > 0 && len(cascade) > 0 {
245+
handler.Logger.Errorw("request err, PatchCdPipeline", "err", fmt.Errorf("cannot perform both cascade and force delete"), "payload", cdPipeline)
246+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
247+
return
248+
}
242249
if len(force) > 0 {
243250
forceDelete, err = strconv.ParseBool(force)
244251
if err != nil {
245252
handler.Logger.Errorw("request err, PatchCdPipeline", "err", err, "payload", cdPipeline)
246253
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
247254
return
248255
}
256+
} else if len(cascade) > 0 {
257+
cascadeDelete, err = strconv.ParseBool(cascade)
258+
if err != nil {
259+
handler.Logger.Errorw("request err, PatchCdPipeline", "err", err, "payload", cdPipeline)
260+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
261+
return
262+
}
249263
}
250264
cdPipeline.ForceDelete = forceDelete
265+
cdPipeline.NonCascadeDelete = !cascadeDelete
251266
handler.Logger.Infow("request payload, PatchCdPipeline", "payload", cdPipeline)
252267
err = handler.validator.StructPartial(cdPipeline, "AppId", "Action")
253268
if err == nil {

0 commit comments

Comments
 (0)