Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show deployment status and timeline for helm apps deployed via gitops #3299

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
607876b
show deployment status and timeline for helm apps deployed via gitops…
prakash100198 Mar 29, 2023
00b78b2
sql scripts added and minor fixes
prakash100198 Apr 4, 2023
a349ee2
merge main into show-deployment-status-and-timeline branch
prakash100198 Apr 4, 2023
2c09f75
multiple bug fixes and sql script fix
prakash100198 Apr 6, 2023
d8e0797
Merge branch 'main' into show-deployment-status-and-timeline-for-helm…
prakash100198 Apr 6, 2023
291b503
nil pointer fix in CdHandler
prakash100198 Apr 6, 2023
8a75c45
debugging commit
prakash100198 Apr 7, 2023
2576950
updated resource tree call for installed apps, to allow sync the depl…
prakash100198 Apr 7, 2023
169c24c
pipeline status timeline terminal status update code and condition ad…
prakash100198 Apr 10, 2023
4612591
minor check added
prakash100198 Apr 10, 2023
27efa09
terminal state for installed app version history incorporated
prakash100198 Apr 10, 2023
5183ef4
minor fix
prakash100198 Apr 10, 2023
806a903
minor fix
prakash100198 Apr 10, 2023
38e5e4f
fix for update of installed app
prakash100198 Apr 10, 2023
2e01f43
updation of installed helm app case handled
prakash100198 Apr 12, 2023
6bd0213
updation of installed helm app case handled
prakash100198 Apr 12, 2023
faa50b8
minor sql script fix
prakash100198 Apr 12, 2023
2fd5ac1
minor fix
prakash100198 Apr 12, 2023
fc871c0
minor fix
prakash100198 Apr 12, 2023
843d6e5
kubewatch topic subscribe gitops repo name check for app store check …
prakash100198 Apr 12, 2023
cc82597
location changed for git status timeline update for helm app update
prakash100198 Apr 12, 2023
8c33456
pipeline timeline and version history for bulk deploy helm chart bug fix
prakash100198 Apr 17, 2023
b4fedfd
merge main with show-deployment-status-and-timeline-for-helm-apps-via…
prakash100198 Apr 18, 2023
36ba898
Merge branch 'main' into show-deployment-status-and-timeline-for-helm…
prakash100198 Apr 18, 2023
03db26a
sync timeline status activity scoped under goroutine and sync pipelin…
prakash100198 Apr 18, 2023
51414f1
merge branch 'main' with show-deployment-status-and-timeline-for-helm…
prakash100198 Apr 19, 2023
760c237
code review from subhashish incorporated
prakash100198 Apr 24, 2023
41b2e8a
merge main in show-deployment-status-and-timeline... branch
prakash100198 Apr 24, 2023
187f9ea
sql script number change
prakash100198 Apr 24, 2023
790cc53
Merge branch 'main' into show-deployment-status-and-timeline-for-helm…
prakash100198 Apr 25, 2023
dc9bf49
sql script number change
prakash100198 Apr 25, 2023
be64350
cdhandler wire issue fix
prakash100198 Apr 25, 2023
9bfbd84
Merge branch 'main' into show-deployment-status-and-timeline-for-helm…
prakash100198 Apr 28, 2023
9c94dd3
merging main here
prakash100198 May 1, 2023
eb4f272
incorporating vikram's review comments
prakash100198 May 1, 2023
02ab39c
Deployment history page deploy API case incorporated
prakash100198 May 2, 2023
c6e270e
Deployment pipeline status failed and superseded status incorporated
prakash100198 May 2, 2023
7e498b1
status field added in deployment history for a particular installed app
prakash100198 May 2, 2023
210a19b
minor fix
prakash100198 May 2, 2023
2d8a0b3
deployment app type field added in deployent history call for identif…
prakash100198 May 2, 2023
0535762
minor fix
prakash100198 May 2, 2023
4ccc227
minor fix- update failed status after rollback application from deplo…
prakash100198 May 2, 2023
d373a89
minor fix
prakash100198 May 2, 2023
49fba84
Merge branch 'main' into show-deployment-status-and-timeline-for-helm…
prakash100198 May 2, 2023
62dfafc
sql script change
prakash100198 May 2, 2023
198cd14
minor fix
prakash100198 May 3, 2023
51bb13f
fix for deployment page histories time getting updated when new deplo…
prakash100198 May 3, 2023
1a6ba39
fix for bulk chart deploy via helm
prakash100198 May 3, 2023
80bd037
Merge branch 'main' into show-deployment-status-and-timeline-for-helm…
prakash100198 May 7, 2023
52b18f5
sql script number change
prakash100198 May 7, 2023
7c64d5a
minor fix
prakash100198 May 7, 2023
3c46118
minor fix for wrong installedAppVersionId getting stored in version h…
prakash100198 May 8, 2023
4120098
reverted previous and fix for wrong installedAppVersionId getting sto…
prakash100198 May 8, 2023
e431aef
minor fix for ensuring not-null constraint in installedAppVersionHist…
prakash100198 May 8, 2023
07f372b
removed unnecessary goroutine calls while fetching installed app deta…
prakash100198 May 8, 2023
5f5213c
updating of installedAppVersion table done before every operation in …
prakash100198 May 8, 2023
19e5ae0
onupdate previous deployments cancelling event incorporated
prakash100198 May 8, 2023
e56bf69
update chart with new/old versions previous deployments cancelling ev…
prakash100198 May 8, 2023
e565a33
minor fix
prakash100198 May 8, 2023
63fb492
minor fix
prakash100198 May 8, 2023
b003c9d
Merge branch 'main' into show-deployment-status-and-timeline-for-helm…
prakash100198 May 8, 2023
7062fc7
revert prev fix
prakash100198 May 8, 2023
d5b5b29
remove unused func from repo layer
prakash100198 May 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions Wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import (
"github.com/devtron-labs/devtron/internal/util"
"github.com/devtron-labs/devtron/internal/util/ArgoUtil"
"github.com/devtron-labs/devtron/pkg/app"
"github.com/devtron-labs/devtron/pkg/app/status"
"github.com/devtron-labs/devtron/pkg/appClone"
"github.com/devtron-labs/devtron/pkg/appClone/batch"
"github.com/devtron-labs/devtron/pkg/appGroup"
Expand Down Expand Up @@ -459,7 +460,8 @@ func InitializeApp() (*App, error) {

notifier.NewNotificationConfigBuilderImpl,
wire.Bind(new(notifier.NotificationConfigBuilder), new(*notifier.NotificationConfigBuilderImpl)),

appStoreRestHandler.NewAppStoreStatusTimelineRestHandlerImpl,
wire.Bind(new(appStoreRestHandler.AppStoreStatusTimelineRestHandler), new(*appStoreRestHandler.AppStoreStatusTimelineRestHandlerImpl)),
appStoreRestHandler.NewInstalledAppRestHandlerImpl,
wire.Bind(new(appStoreRestHandler.InstalledAppRestHandler), new(*appStoreRestHandler.InstalledAppRestHandlerImpl)),
service.NewInstalledAppServiceImpl,
Expand Down Expand Up @@ -774,8 +776,8 @@ func InitializeApp() (*App, error) {
restHandler.NewPipelineStatusTimelineRestHandlerImpl,
wire.Bind(new(restHandler.PipelineStatusTimelineRestHandler), new(*restHandler.PipelineStatusTimelineRestHandlerImpl)),

app.NewPipelineStatusTimelineServiceImpl,
wire.Bind(new(app.PipelineStatusTimelineService), new(*app.PipelineStatusTimelineServiceImpl)),
status.NewPipelineStatusTimelineServiceImpl,
wire.Bind(new(status.PipelineStatusTimelineService), new(*status.PipelineStatusTimelineServiceImpl)),

router.NewUserAttributesRouterImpl,
wire.Bind(new(router.UserAttributesRouter), new(*router.UserAttributesRouterImpl)),
Expand Down Expand Up @@ -811,13 +813,13 @@ func InitializeApp() (*App, error) {
chartRepoRepository.NewGlobalStrategyMetadataChartRefMappingRepositoryImpl,
wire.Bind(new(chartRepoRepository.GlobalStrategyMetadataChartRefMappingRepository), new(*chartRepoRepository.GlobalStrategyMetadataChartRefMappingRepositoryImpl)),

app.NewPipelineStatusTimelineResourcesServiceImpl,
wire.Bind(new(app.PipelineStatusTimelineResourcesService), new(*app.PipelineStatusTimelineResourcesServiceImpl)),
status.NewPipelineStatusTimelineResourcesServiceImpl,
wire.Bind(new(status.PipelineStatusTimelineResourcesService), new(*status.PipelineStatusTimelineResourcesServiceImpl)),
pipelineConfig.NewPipelineStatusTimelineResourcesRepositoryImpl,
wire.Bind(new(pipelineConfig.PipelineStatusTimelineResourcesRepository), new(*pipelineConfig.PipelineStatusTimelineResourcesRepositoryImpl)),

app.NewPipelineStatusSyncDetailServiceImpl,
wire.Bind(new(app.PipelineStatusSyncDetailService), new(*app.PipelineStatusSyncDetailServiceImpl)),
status.NewPipelineStatusSyncDetailServiceImpl,
wire.Bind(new(status.PipelineStatusSyncDetailService), new(*status.PipelineStatusSyncDetailServiceImpl)),
pipelineConfig.NewPipelineStatusSyncDetailRepositoryImpl,
wire.Bind(new(pipelineConfig.PipelineStatusSyncDetailRepository), new(*pipelineConfig.PipelineStatusSyncDetailRepositoryImpl)),

Expand Down
25 changes: 16 additions & 9 deletions api/appStore/AppStoreRouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,34 @@ type AppStoreRouter interface {
}

type AppStoreRouterImpl struct {
deployRestHandler InstalledAppRestHandler
appStoreValuesRouter appStoreValues.AppStoreValuesRouter
appStoreDiscoverRouter appStoreDiscover.AppStoreDiscoverRouter
appStoreDeploymentRouter appStoreDeployment.AppStoreDeploymentRouter
deployRestHandler InstalledAppRestHandler
appStoreValuesRouter appStoreValues.AppStoreValuesRouter
appStoreDiscoverRouter appStoreDiscover.AppStoreDiscoverRouter
appStoreDeploymentRouter appStoreDeployment.AppStoreDeploymentRouter
appStoreStatusTimelineRestHandler AppStoreStatusTimelineRestHandler
}

func NewAppStoreRouterImpl(restHandler InstalledAppRestHandler,
appStoreValuesRouter appStoreValues.AppStoreValuesRouter, appStoreDiscoverRouter appStoreDiscover.AppStoreDiscoverRouter,
appStoreDeploymentRouter appStoreDeployment.AppStoreDeploymentRouter) *AppStoreRouterImpl {
appStoreDeploymentRouter appStoreDeployment.AppStoreDeploymentRouter,
appStoreStatusTimelineRestHandler AppStoreStatusTimelineRestHandler) *AppStoreRouterImpl {
return &AppStoreRouterImpl{
deployRestHandler: restHandler,
appStoreValuesRouter: appStoreValuesRouter,
appStoreDiscoverRouter: appStoreDiscoverRouter,
appStoreDeploymentRouter: appStoreDeploymentRouter,
deployRestHandler: restHandler,
appStoreValuesRouter: appStoreValuesRouter,
appStoreDiscoverRouter: appStoreDiscoverRouter,
appStoreDeploymentRouter: appStoreDeploymentRouter,
appStoreStatusTimelineRestHandler: appStoreStatusTimelineRestHandler,
}
}

func (router AppStoreRouterImpl) Init(configRouter *mux.Router) {
// deployment router starts
appStoreDeploymentSubRouter := configRouter.PathPrefix("/deployment").Subrouter()
router.appStoreDeploymentRouter.Init(appStoreDeploymentSubRouter)

configRouter.Path("/deployment-status/timeline/{installedAppId}/{envId}").
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

installed app id is already mapped with environment id

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm directly getting installedAppVersion from these two ids, hence saving one db call in which I would have needed to fetch installed apps from db then get env id from installed app object then pass env id to function

HandlerFunc(router.appStoreStatusTimelineRestHandler.FetchTimelinesForAppStore).Methods("GET")

// deployment router ends

// values router starts
Expand Down
74 changes: 74 additions & 0 deletions api/appStore/AppStoreStatusTimelineRestHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package appStore

import (
"fmt"
"github.com/devtron-labs/devtron/api/restHandler/common"
"github.com/devtron-labs/devtron/pkg/app/status"
"github.com/devtron-labs/devtron/pkg/user/casbin"
"github.com/devtron-labs/devtron/util/rbac"
"github.com/gorilla/mux"
"go.uber.org/zap"
"net/http"
"strconv"
)

type AppStoreStatusTimelineRestHandler interface {
FetchTimelinesForAppStore(w http.ResponseWriter, r *http.Request)
}

type AppStoreStatusTimelineRestHandlerImpl struct {
logger *zap.SugaredLogger
pipelineStatusTimelineService status.PipelineStatusTimelineService
enforcerUtil rbac.EnforcerUtil
enforcer casbin.Enforcer
}

func NewAppStoreStatusTimelineRestHandlerImpl(logger *zap.SugaredLogger,
pipelineStatusTimelineService status.PipelineStatusTimelineService,
enforcerUtil rbac.EnforcerUtil,
enforcer casbin.Enforcer) *AppStoreStatusTimelineRestHandlerImpl {
return &AppStoreStatusTimelineRestHandlerImpl{
logger: logger,
pipelineStatusTimelineService: pipelineStatusTimelineService,
enforcerUtil: enforcerUtil,
enforcer: enforcer,
}
}

func (handler AppStoreStatusTimelineRestHandlerImpl) FetchTimelinesForAppStore(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
installedAppId, err := strconv.Atoi(vars["installedAppId"])
if err != nil {
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return
}
envId, err := strconv.Atoi(vars["envId"])
if err != nil {
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return
}
installedAppVersionHistoryId := 0
installedAppVersionHistoryIdParam := r.URL.Query().Get("installedAppVersionHistoryId")
if len(installedAppVersionHistoryIdParam) != 0 {
installedAppVersionHistoryId, err = strconv.Atoi(installedAppVersionHistoryIdParam)
if err != nil {
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return
}
}
resourceName := handler.enforcerUtil.GetAppRBACNameByAppId(installedAppId)
token := r.Header.Get("token")
if ok := handler.enforcer.Enforce(token, casbin.ResourceApplications, casbin.ActionGet, resourceName); !ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to check environment enforcer

common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), "Unauthorized User", http.StatusForbidden)
return
}

timelines, err := handler.pipelineStatusTimelineService.FetchTimelinesForAppStore(installedAppId, envId, installedAppVersionHistoryId)
if err != nil {
handler.logger.Errorw("error in getting pipeline status timelines by installedAppVersionHistoryId", "err", err, "installedAppVersionHistoryId", installedAppVersionHistoryId, "installedAppId", installedAppId, "envId", envId)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, err, timelines, http.StatusOK)
return
}
76 changes: 52 additions & 24 deletions api/appStore/InstalledAppRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
openapi "github.com/devtron-labs/devtron/api/helm-app/openapiClient"
"github.com/devtron-labs/devtron/api/restHandler/common"
"github.com/devtron-labs/devtron/client/argocdServer/application"
"github.com/devtron-labs/devtron/client/cron"
"github.com/devtron-labs/devtron/internal/constants"
"github.com/devtron-labs/devtron/internal/middleware"
util2 "github.com/devtron-labs/devtron/internal/util"
Expand Down Expand Up @@ -61,39 +62,45 @@ type InstalledAppRestHandler interface {
}

type InstalledAppRestHandlerImpl struct {
Logger *zap.SugaredLogger
userAuthService user.UserService
enforcer casbin.Enforcer
enforcerUtil rbac.EnforcerUtil
installedAppService service.InstalledAppService
validator *validator.Validate
clusterService cluster.ClusterService
acdServiceClient application.ServiceClient
appStoreDeploymentService service.AppStoreDeploymentService
helmAppClient client.HelmAppClient
helmAppService client.HelmAppService
argoUserService argo.ArgoUserService
Logger *zap.SugaredLogger
userAuthService user.UserService
enforcer casbin.Enforcer
enforcerUtil rbac.EnforcerUtil
installedAppService service.InstalledAppService
validator *validator.Validate
clusterService cluster.ClusterService
acdServiceClient application.ServiceClient
appStoreDeploymentService service.AppStoreDeploymentService
helmAppClient client.HelmAppClient
helmAppService client.HelmAppService
argoUserService argo.ArgoUserService
cdApplicationStatusUpdateHandler cron.CdApplicationStatusUpdateHandler
installedAppRepository repository.InstalledAppRepository
}

func NewInstalledAppRestHandlerImpl(Logger *zap.SugaredLogger, userAuthService user.UserService,
enforcer casbin.Enforcer, enforcerUtil rbac.EnforcerUtil, installedAppService service.InstalledAppService,
validator *validator.Validate, clusterService cluster.ClusterService, acdServiceClient application.ServiceClient,
appStoreDeploymentService service.AppStoreDeploymentService, helmAppClient client.HelmAppClient, helmAppService client.HelmAppService,
argoUserService argo.ArgoUserService,
cdApplicationStatusUpdateHandler cron.CdApplicationStatusUpdateHandler,
installedAppRepository repository.InstalledAppRepository,
) *InstalledAppRestHandlerImpl {
return &InstalledAppRestHandlerImpl{
Logger: Logger,
userAuthService: userAuthService,
enforcer: enforcer,
enforcerUtil: enforcerUtil,
installedAppService: installedAppService,
validator: validator,
clusterService: clusterService,
acdServiceClient: acdServiceClient,
appStoreDeploymentService: appStoreDeploymentService,
helmAppService: helmAppService,
helmAppClient: helmAppClient,
argoUserService: argoUserService,
Logger: Logger,
userAuthService: userAuthService,
enforcer: enforcer,
enforcerUtil: enforcerUtil,
installedAppService: installedAppService,
validator: validator,
clusterService: clusterService,
acdServiceClient: acdServiceClient,
appStoreDeploymentService: appStoreDeploymentService,
helmAppService: helmAppService,
helmAppClient: helmAppClient,
argoUserService: argoUserService,
cdApplicationStatusUpdateHandler: cdApplicationStatusUpdateHandler,
installedAppRepository: installedAppRepository,
}
}

Expand Down Expand Up @@ -594,13 +601,21 @@ func (handler *InstalledAppRestHandlerImpl) FetchResourceTree(w http.ResponseWri
common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), nil, http.StatusForbidden)
return
}
appDetail, err := handler.installedAppService.FindAppDetailsForAppstoreApplication(installedAppId, envId)
if err != nil {
handler.Logger.Errorw("service err, FetchAppDetailsForInstalledApp, app store", "err", err, "installedAppId", installedAppId, "envId", envId)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}

resourceTreeAndNotesContainer := bean2.ResourceTreeAndNotesContainer{}
resourceTreeAndNotesContainer.ResourceTree = map[string]interface{}{}

if len(installedApp.App.AppName) > 0 && len(installedApp.Environment.Name) > 0 {
err = handler.fetchResourceTree(w, r, &resourceTreeAndNotesContainer, *installedApp)
if installedApp.DeploymentAppType == util2.PIPELINE_DEPLOYMENT_TYPE_ACD {
//resource tree has been fetched now prepare to sync application deployment status with this resource tree call
handler.syncDeploymentStatusWithResourceTreeCall(appDetail)
apiError, ok := err.(*util2.ApiError)
if ok && apiError != nil {
if apiError.Code == constants.AppDetailResourceTreeNotFound && installedApp.DeploymentAppDeleteRequest == true {
Expand All @@ -620,6 +635,19 @@ func (handler *InstalledAppRestHandlerImpl) FetchResourceTree(w http.ResponseWri
common.WriteJsonResp(w, nil, resourceTreeAndNotesContainer, http.StatusOK)
}

func (handler *InstalledAppRestHandlerImpl) syncDeploymentStatusWithResourceTreeCall(appDetail bean2.AppDetailContainer) {
go func() {
installedAppVersion, err := handler.installedAppRepository.GetInstalledAppVersion(appDetail.AppStoreInstalledAppVersionId)
if err != nil {
handler.Logger.Errorw("error in getting installed_app_version in FetchAppDetailsForInstalledApp", "err", err)
}
err = handler.cdApplicationStatusUpdateHandler.SyncPipelineStatusForAppStoreForResourceTreeCall(installedAppVersion)
if err != nil {
handler.Logger.Errorw("error in syncing deployment status for installed_app ", "err", err, "installedAppVersion", installedAppVersion)
}
}()
}

func (handler *InstalledAppRestHandlerImpl) FetchResourceTreeForACDApp(w http.ResponseWriter, r *http.Request) {
userId, err := handler.userAuthService.GetLoggedInUser(r)
if userId == 0 || err != nil {
Expand Down
7 changes: 7 additions & 0 deletions api/appStore/deployment/AppStoreDeploymentRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"net/http"
"strconv"
"strings"
"time"
)

const HELM_APP_UPDATE_COUNTER = "HelmAppUpdateCounter"
Expand Down Expand Up @@ -463,6 +464,7 @@ func (handler AppStoreDeploymentRestHandlerImpl) UpdateInstalledApp(w http.Respo
}
ctx = context.WithValue(r.Context(), "token", acdToken)
}
triggeredAt := time.Now()
res, err := handler.appStoreDeploymentService.UpdateInstalledApp(ctx, &request)
if err != nil {
if strings.Contains(err.Error(), "application spec is invalid") {
Expand All @@ -472,6 +474,11 @@ func (handler AppStoreDeploymentRestHandlerImpl) UpdateInstalledApp(w http.Respo
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
err1 := handler.appStoreDeploymentService.UpdatePreviousDeploymentStatusForAppStore(res, triggeredAt, err)
if err1 != nil {
handler.Logger.Errorw("error while update previous installed app version history", "err", err, "installAppVersionRequest", res)
//if installed app is updated and error is in updating previous deployment status, then don't block user, just show error.
}

err = handler.attributesService.UpdateKeyValueByOne(HELM_APP_UPDATE_COUNTER)

Expand Down
1 change: 1 addition & 0 deletions api/helm-app/HelmAppRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,4 +464,5 @@ type InstalledAppInfo struct {
AppStoreChartName string `json:"appStoreChartName"`
TeamId int `json:"teamId"`
TeamName string `json:"teamName"`
DeploymentType string `json:"deploymentType"`
}
Loading