Skip to content

Commit 8994eec

Browse files
feat: deployment pipeline partially delete. (#2950)
* wip * wip * added deployment app delete request flag for both cd pipeline and installed app, added new request for paritally delete * wip: adding handling if kubewatch delete status is lost * temp commit * shifting code from temp branch to main branch * wip: temp change * wip: temp change * migration scripts * migration scripts * migration scripts * adding app delete check * adding deployment app created flag * removing app delete check * refactoring and corner case handle * reverting schemas * wip: fixes after refactoring * updating migration scripts * updating vendor file * adding back argocd file * updating vendor * adding back asset folder * fixing merge errors * correcting sql script sequence no * skipping app status deletion if not found in database --------- Co-authored-by: vikramdevtron <[email protected]>
1 parent f12eae0 commit 8994eec

38 files changed

+883
-348
lines changed

Wire.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,8 @@ func InitializeApp() (*App, error) {
407407
pubsub.NewWorkflowStatusUpdateHandlerImpl,
408408
wire.Bind(new(pubsub.WorkflowStatusUpdateHandler), new(*pubsub.WorkflowStatusUpdateHandlerImpl)),
409409

410-
pubsub.NewApplicationStatusUpdateHandlerImpl,
411-
wire.Bind(new(pubsub.ApplicationStatusUpdateHandler), new(*pubsub.ApplicationStatusUpdateHandlerImpl)),
410+
pubsub.NewApplicationStatusHandlerImpl,
411+
wire.Bind(new(pubsub.ApplicationStatusHandler), new(*pubsub.ApplicationStatusHandlerImpl)),
412412

413413
pubsub.NewCiEventHandlerImpl,
414414
wire.Bind(new(pubsub.CiEventHandler), new(*pubsub.CiEventHandlerImpl)),

api/appStore/InstalledAppRestHandler.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
openapi "github.com/devtron-labs/devtron/api/helm-app/openapiClient"
2727
"github.com/devtron-labs/devtron/api/restHandler/common"
2828
"github.com/devtron-labs/devtron/client/argocdServer/application"
29+
"github.com/devtron-labs/devtron/internal/constants"
30+
util2 "github.com/devtron-labs/devtron/internal/util"
2931
appStoreBean "github.com/devtron-labs/devtron/pkg/appStore/bean"
3032
"github.com/devtron-labs/devtron/pkg/appStore/deployment/service"
3133
"github.com/devtron-labs/devtron/pkg/cluster"
@@ -34,6 +36,7 @@ import (
3436
"github.com/devtron-labs/devtron/util/argo"
3537
"github.com/devtron-labs/devtron/util/rbac"
3638
"github.com/devtron-labs/devtron/util/response"
39+
"github.com/go-pg/pg"
3740
"github.com/gorilla/mux"
3841
"go.uber.org/zap"
3942
"gopkg.in/go-playground/validator.v9"
@@ -410,6 +413,12 @@ func (handler *InstalledAppRestHandlerImpl) FetchAppDetailsForInstalledApp(w htt
410413
}
411414
handler.Logger.Infow("request payload, FetchAppDetailsForInstalledApp, app store", "installedAppId", installedAppId, "envId", envId)
412415

416+
err = handler.installedAppService.CheckAppExistsByInstalledAppId(installedAppId)
417+
if err == pg.ErrNoRows {
418+
common.WriteJsonResp(w, err, "App not found in database", http.StatusBadRequest)
419+
return
420+
}
421+
413422
appDetail, err := handler.installedAppService.FindAppDetailsForAppstoreApplication(installedAppId, envId)
414423
if err != nil {
415424
handler.Logger.Errorw("service err, FetchAppDetailsForInstalledApp, app store", "err", err, "installedAppId", installedAppId, "envId", envId)
@@ -434,16 +443,34 @@ func (handler *InstalledAppRestHandlerImpl) FetchAppDetailsForInstalledApp(w htt
434443
}
435444
//rback block ends here
436445
if len(appDetail.AppName) > 0 && len(appDetail.EnvironmentName) > 0 {
437-
handler.fetchResourceTree(w, r, &appDetail)
446+
err = handler.fetchResourceTree(w, r, &appDetail)
447+
if appDetail.DeploymentAppType == util2.PIPELINE_DEPLOYMENT_TYPE_ACD {
448+
apiError, ok := err.(*util2.ApiError)
449+
if ok && apiError != nil {
450+
if apiError.Code == constants.AppDetailResourceTreeNotFound && appDetail.DeploymentAppDeleteRequest == true {
451+
err = handler.installedAppService.MarkGitOpsInstalledAppsDeletedIfArgoAppIsDeleted(installedAppId, envId)
452+
appDeleteErr, appDeleteErrOk := err.(*util2.ApiError)
453+
if appDeleteErrOk && appDeleteErr != nil {
454+
common.WriteJsonResp(w, fmt.Errorf(appDeleteErr.InternalMessage), nil, appDeleteErr.HttpStatusCode)
455+
return
456+
}
457+
}
458+
}
459+
} else if err != nil {
460+
common.WriteJsonResp(w, fmt.Errorf("error in fetching resource tree"), nil, http.StatusInternalServerError)
461+
return
462+
}
463+
438464
} else {
439465
appDetail.ResourceTree = map[string]interface{}{}
440466
handler.Logger.Warnw("appName and envName not found - avoiding resource tree call", "app", appDetail.AppName, "env", appDetail.EnvironmentName)
441467
}
442-
common.WriteJsonResp(w, err, appDetail, http.StatusOK)
468+
common.WriteJsonResp(w, nil, appDetail, http.StatusOK)
443469
}
444470

445-
func (handler *InstalledAppRestHandlerImpl) fetchResourceTree(w http.ResponseWriter, r *http.Request, appDetail *bean2.AppDetailContainer) {
471+
func (handler *InstalledAppRestHandlerImpl) fetchResourceTree(w http.ResponseWriter, r *http.Request, appDetail *bean2.AppDetailContainer) error {
446472
ctx := r.Context()
447473
cn, _ := w.(http.CloseNotifier)
448-
handler.installedAppService.FetchResourceTree(ctx, cn, appDetail)
474+
_, err := handler.installedAppService.FetchResourceTree(ctx, cn, appDetail)
475+
return err
449476
}

api/appStore/deployment/AppStoreDeploymentRestHandler.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,11 +258,20 @@ func (handler AppStoreDeploymentRestHandlerImpl) DeleteInstalledApp(w http.Respo
258258
if len(force) > 0 {
259259
forceDelete, err = strconv.ParseBool(force)
260260
if err != nil {
261-
handler.Logger.Errorw("request err, DeleteInstalledApp", "err", err, "installAppId", installAppId)
262261
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
263262
return
264263
}
265264
}
265+
partialDelete := false
266+
partialDeleteStr := v.Get("partialDelete")
267+
if len(partialDeleteStr) > 0 {
268+
partialDelete, err = strconv.ParseBool(partialDeleteStr)
269+
if err != nil {
270+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
271+
return
272+
}
273+
}
274+
266275
handler.Logger.Infow("request payload, DeleteInstalledApp", "installAppId", installAppId)
267276
token := r.Header.Get("token")
268277
installedApp, err := handler.appStoreDeploymentService.GetInstalledApp(installAppId)
@@ -294,7 +303,7 @@ func (handler AppStoreDeploymentRestHandlerImpl) DeleteInstalledApp(w http.Respo
294303
}
295304
//rbac block ends here
296305

297-
request := appStoreBean.InstallAppVersionDTO{}
306+
request := &appStoreBean.InstallAppVersionDTO{}
298307
request.InstalledAppId = installAppId
299308
request.AppName = installedApp.AppName
300309
request.AppId = installedApp.AppId
@@ -304,6 +313,7 @@ func (handler AppStoreDeploymentRestHandlerImpl) DeleteInstalledApp(w http.Respo
304313
request.AppOfferingMode = installedApp.AppOfferingMode
305314
request.ClusterId = installedApp.ClusterId
306315
request.Namespace = installedApp.Namespace
316+
request.AcdPartialDelete = partialDelete
307317
ctx, cancel := context.WithCancel(r.Context())
308318
if cn, ok := w.(http.CloseNotifier); ok {
309319
go func(done <-chan struct{}, closed <-chan bool) {
@@ -325,13 +335,15 @@ func (handler AppStoreDeploymentRestHandlerImpl) DeleteInstalledApp(w http.Respo
325335
}
326336
ctx = context.WithValue(r.Context(), "token", acdToken)
327337
}
328-
res, err := handler.appStoreDeploymentService.DeleteInstalledApp(ctx, &request)
338+
339+
request, err = handler.appStoreDeploymentService.DeleteInstalledApp(ctx, request)
329340
if err != nil {
330341
handler.Logger.Errorw("service err, DeleteInstalledApp", "err", err, "installAppId", installAppId)
331342
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
332343
return
333344
}
334-
common.WriteJsonResp(w, err, res, http.StatusOK)
345+
346+
common.WriteJsonResp(w, err, request, http.StatusOK)
335347
}
336348

337349
func (handler *AppStoreDeploymentRestHandlerImpl) LinkHelmApplicationToChartStore(w http.ResponseWriter, r *http.Request) {

api/bean/AppView.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ type DeploymentDetailContainer struct {
113113
ClusterName string `json:"clusterName,omitempty"`
114114
DockerRegistryId string `json:"dockerRegistryId,omitempty"`
115115
IpsAccessProvided bool `json:"ipsAccessProvided"`
116+
DeploymentAppDeleteRequest bool `json:"deploymentAppDeleteRequest"`
116117
}
117118

118119
type AppDetailContainer struct {
@@ -124,14 +125,15 @@ type AppDetailContainer struct {
124125
}
125126

126127
type Environment struct {
127-
AppStatus string `json:"appStatus"` //this is not the status of environment , this make sense with a specific app only
128-
EnvironmentId int `json:"environmentId"`
129-
EnvironmentName string `json:"environmentName"`
130-
AppMetrics *bool `json:"appMetrics"`
131-
InfraMetrics *bool `json:"infraMetrics"`
132-
Prod bool `json:"prod"`
133-
ChartRefId int `json:"chartRefId"`
134-
LastDeployed string `json:"lastDeployed"`
128+
AppStatus string `json:"appStatus"` //this is not the status of environment , this make sense with a specific app only
129+
EnvironmentId int `json:"environmentId"`
130+
EnvironmentName string `json:"environmentName"`
131+
AppMetrics *bool `json:"appMetrics"`
132+
InfraMetrics *bool `json:"infraMetrics"`
133+
Prod bool `json:"prod"`
134+
ChartRefId int `json:"chartRefId"`
135+
LastDeployed string `json:"lastDeployed"`
136+
DeploymentAppDeleteRequest bool `json:"deploymentAppDeleteRequest"`
135137
}
136138

137139
type InstanceDetail struct {

api/restHandler/AppListingRestHandler.go

Lines changed: 68 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/devtron-labs/devtron/api/restHandler/common"
3030
"github.com/devtron-labs/devtron/client/argocdServer/application"
3131
"github.com/devtron-labs/devtron/client/cron"
32+
"github.com/devtron-labs/devtron/internal/constants"
3233
"github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig"
3334
"github.com/devtron-labs/devtron/internal/util"
3435
"github.com/devtron-labs/devtron/pkg/app"
@@ -44,6 +45,7 @@ import (
4445
"github.com/devtron-labs/devtron/util/argo"
4546
"github.com/devtron-labs/devtron/util/k8s"
4647
"github.com/devtron-labs/devtron/util/rbac"
48+
"github.com/go-pg/pg"
4749
"github.com/gorilla/mux"
4850
"go.opentelemetry.io/otel"
4951
"go.uber.org/zap"
@@ -355,6 +357,20 @@ func (handler AppListingRestHandlerImpl) FetchAppDetails(w http.ResponseWriter,
355357
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
356358
return
357359
}
360+
pipelines, err := handler.pipelineRepository.FindActiveByAppIdAndEnvironmentId(appId, envId)
361+
if err == pg.ErrNoRows {
362+
common.WriteJsonResp(w, err, "pipeline Not found in database", http.StatusNotFound)
363+
return
364+
}
365+
if len(pipelines) == 0 {
366+
common.WriteJsonResp(w, fmt.Errorf("app deleted"), nil, http.StatusNotFound)
367+
return
368+
}
369+
if len(pipelines) != 1 {
370+
common.WriteJsonResp(w, err, "multiple pipelines found for an envId", http.StatusBadRequest)
371+
return
372+
}
373+
cdPipeline := pipelines[0]
358374
appDetail, err := handler.appListingService.FetchAppDetails(r.Context(), appId, envId)
359375
if err != nil {
360376
handler.logger.Errorw("service err, FetchAppDetails", "err", err, "appId", appId, "envId", envId)
@@ -367,7 +383,31 @@ func (handler AppListingRestHandlerImpl) FetchAppDetails(w http.ResponseWriter,
367383
common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), nil, http.StatusForbidden)
368384
return
369385
}
370-
appDetail = handler.fetchResourceTree(w, r, appId, envId, appDetail)
386+
acdToken, err := handler.argoUserService.GetLatestDevtronArgoCdUserToken()
387+
if err != nil {
388+
common.WriteJsonResp(w, fmt.Errorf("error in getting acd token"), nil, http.StatusInternalServerError)
389+
return
390+
}
391+
appDetail, err = handler.fetchResourceTree(w, r, appId, envId, appDetail, acdToken, cdPipeline)
392+
if appDetail.DeploymentAppType == util.PIPELINE_DEPLOYMENT_TYPE_ACD {
393+
apiError, ok := err.(*util.ApiError)
394+
if ok && apiError != nil {
395+
if apiError.Code == constants.AppDetailResourceTreeNotFound && appDetail.DeploymentAppDeleteRequest == true {
396+
acdAppFound, _ := handler.pipeline.MarkGitOpsDevtronAppsDeletedWhereArgoAppIsDeleted(appId, envId, acdToken, cdPipeline)
397+
if acdAppFound {
398+
common.WriteJsonResp(w, fmt.Errorf("unable to fetch resource tree"), nil, http.StatusInternalServerError)
399+
return
400+
} else {
401+
common.WriteJsonResp(w, fmt.Errorf("app deleted"), nil, http.StatusNotFound)
402+
return
403+
}
404+
}
405+
}
406+
}
407+
if err != nil {
408+
common.WriteJsonResp(w, fmt.Errorf("unable to fetch resource tree"), nil, http.StatusInternalServerError)
409+
return
410+
}
371411
common.WriteJsonResp(w, err, appDetail, http.StatusOK)
372412
}
373413

@@ -624,7 +664,8 @@ func (handler AppListingRestHandlerImpl) RedirectToLinkouts(w http.ResponseWrite
624664
func (handler AppListingRestHandlerImpl) fetchResourceTreeFromInstallAppService(w http.ResponseWriter, r *http.Request, appDetail bean.AppDetailContainer) bean.AppDetailContainer {
625665
rctx := r.Context()
626666
cn, _ := w.(http.CloseNotifier)
627-
return handler.installedAppService.FetchResourceTree(rctx, cn, &appDetail)
667+
_, _ = handler.installedAppService.FetchResourceTree(rctx, cn, &appDetail)
668+
return appDetail
628669
}
629670
func (handler AppListingRestHandlerImpl) GetHostUrlsByBatch(w http.ResponseWriter, r *http.Request) {
630671
vars := r.URL.Query()
@@ -659,6 +700,16 @@ func (handler AppListingRestHandlerImpl) GetHostUrlsByBatch(w http.ResponseWrite
659700
common.WriteJsonResp(w, fmt.Errorf("error in parsing envId : %s must be integer", envIdParam), nil, http.StatusBadRequest)
660701
return
661702
}
703+
pipelines, err := handler.pipelineRepository.FindActiveByAppIdAndEnvironmentId(appId, envId)
704+
if err == pg.ErrNoRows {
705+
common.WriteJsonResp(w, err, "pipeline Not found in database", http.StatusNotFound)
706+
return
707+
}
708+
if len(pipelines) != 1 {
709+
common.WriteJsonResp(w, err, "multiple pipelines found for an envId", http.StatusBadRequest)
710+
return
711+
}
712+
cdPipeline := pipelines[0]
662713
appDetail, err, appId = handler.getAppDetails(r.Context(), appIdParam, installedAppIdParam, envId)
663714
if err != nil {
664715
handler.logger.Errorw("error occurred while getting app details", "appId", appIdParam, "installedAppId", installedAppIdParam, "envId", envId)
@@ -686,7 +737,12 @@ func (handler AppListingRestHandlerImpl) GetHostUrlsByBatch(w http.ResponseWrite
686737
if installedAppIdParam != "" {
687738
appDetail = handler.fetchResourceTreeFromInstallAppService(w, r, appDetail)
688739
} else {
689-
appDetail = handler.fetchResourceTree(w, r, appId, envId, appDetail)
740+
acdToken, err := handler.argoUserService.GetLatestDevtronArgoCdUserToken()
741+
if err != nil {
742+
common.WriteJsonResp(w, fmt.Errorf("error in getting acd token"), nil, http.StatusInternalServerError)
743+
return
744+
}
745+
appDetail, err = handler.fetchResourceTree(w, r, appId, envId, appDetail, acdToken, cdPipeline)
690746
}
691747

692748
resourceTree := appDetail.ResourceTree
@@ -737,20 +793,9 @@ func (handler AppListingRestHandlerImpl) getAppDetails(ctx context.Context, appI
737793
}
738794

739795
// TODO: move this to service
740-
func (handler AppListingRestHandlerImpl) fetchResourceTree(w http.ResponseWriter, r *http.Request, appId int, envId int, appDetail bean.AppDetailContainer) bean.AppDetailContainer {
796+
func (handler AppListingRestHandlerImpl) fetchResourceTree(w http.ResponseWriter, r *http.Request, appId int, envId int, appDetail bean.AppDetailContainer, acdToken string, cdPipeline *pipelineConfig.Pipeline) (bean.AppDetailContainer, error) {
741797
if len(appDetail.AppName) > 0 && len(appDetail.EnvironmentName) > 0 && util.IsAcdApp(appDetail.DeploymentAppType) {
742798
//RBAC enforcer Ends
743-
cdPipelines, err := handler.pipelineRepository.FindActiveByAppIdAndEnvironmentId(appId, envId)
744-
if err != nil {
745-
handler.logger.Errorw("error in getting cdPipeline by appId and envId", "err", err, "appid", appId, "envId", envId)
746-
common.WriteJsonResp(w, err, "", http.StatusInternalServerError)
747-
return appDetail
748-
}
749-
if len(cdPipelines) != 1 {
750-
common.WriteJsonResp(w, err, "", http.StatusInternalServerError)
751-
return appDetail
752-
}
753-
cdPipeline := cdPipelines[0]
754799
query := &application2.ResourcesQuery{
755800
ApplicationName: &cdPipeline.DeploymentAppName,
756801
}
@@ -765,23 +810,18 @@ func (handler AppListingRestHandlerImpl) fetchResourceTree(w http.ResponseWriter
765810
}(ctx.Done(), cn.CloseNotify())
766811
}
767812
defer cancel()
768-
acdToken, err := handler.argoUserService.GetLatestDevtronArgoCdUserToken()
769-
if err != nil {
770-
handler.logger.Errorw("error in getting acd token", "err", err)
771-
common.WriteJsonResp(w, err, "", http.StatusInternalServerError)
772-
return appDetail
773-
}
774813
ctx = context.WithValue(ctx, "token", acdToken)
775814
start := time.Now()
776815
resp, err := handler.application.ResourceTree(ctx, query)
777816
elapsed := time.Since(start)
778817
if err != nil {
779-
handler.logger.Errorw("service err, FetchAppDetails, resource tree",
780-
"err", err,
781-
"app", appId,
782-
"env", envId)
783-
784-
return appDetail
818+
handler.logger.Errorw("service err, FetchAppDetails, resource tree", "err", err, "app", appId, "env", envId)
819+
err = &util.ApiError{
820+
Code: constants.AppDetailResourceTreeNotFound,
821+
InternalMessage: "app detail fetched, failed to get resource tree from acd",
822+
UserMessage: "Error fetching detail, if you have recently created this deployment pipeline please try after sometime.",
823+
}
824+
return appDetail, err
785825
}
786826
if resp.Status == string(health.HealthStatusHealthy) {
787827
status, err := handler.appListingService.ISLastReleaseStopType(appId, envId)
@@ -849,7 +889,7 @@ func (handler AppListingRestHandlerImpl) fetchResourceTree(w http.ResponseWriter
849889
appDetail.ResourceTree = map[string]interface{}{}
850890
handler.logger.Warnw("appName and envName not found - avoiding resource tree call", "app", appDetail.AppName, "env", appDetail.EnvironmentName)
851891
}
852-
return appDetail
892+
return appDetail, nil
853893
}
854894

855895
func (handler AppListingRestHandlerImpl) ManualSyncAcdPipelineDeploymentStatus(w http.ResponseWriter, r *http.Request) {

api/restHandler/app/DeploymentPipelineRestHandler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ func (handler PipelineConfigRestHandlerImpl) PatchCdPipeline(w http.ResponseWrit
245245
err = handler.validator.Struct(cdPipeline.Pipeline)
246246
} else if cdPipeline.Action == bean.CD_DELETE {
247247
err = handler.validator.Var(cdPipeline.Pipeline.Id, "gt=0")
248+
} else if cdPipeline.Action == bean.CD_DELETE_PARTIAL {
249+
err = handler.validator.Var(cdPipeline.Pipeline.Id, "gt=0")
248250
}
249251
}
250252
if err != nil {

0 commit comments

Comments
 (0)