Skip to content

Commit

Permalink
fix: Helm apps entries in Ea mode (#5652)
Browse files Browse the repository at this point in the history
* added the ea apps entry app table

* resolved the ea mode multiple rows error during configuration of app

* modified the ea dockerfile in ca-certificates cmd

* uncommented the code and left the ea helm app making way untouched

* remodified the dockerfile as previous state

* modified the docker file ea mode

* dockerfile exit code 100 due to ap install alternative in ea mode dockerfile

* execute make after main merge

* modified changes in dockerfile ea mode

* resolved comments after first level review

* executed make after merging with develop branch
  • Loading branch information
RajeevRanjan27 authored Aug 21, 2024
1 parent af3133d commit f1aa1fc
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 8 deletions.
78 changes: 73 additions & 5 deletions pkg/app/AppCrudOperationService.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package app
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/caarlos0/env"
client "github.com/devtron-labs/devtron/api/helm-app/service"
Expand Down Expand Up @@ -457,13 +458,67 @@ func convertUrlToHttpsIfSshType(url string) string {
return httpsURL
}

// handleDuplicateAppEntries identifies and resolves duplicate app entries based on creation time.
// It marks the most recent duplicate entry as inactive and updates the corresponding installed app.
func (impl AppCrudOperationServiceImpl) handleDuplicateAppEntries(appNameUniqueIdentifier string) (*appRepository.App, error) {
// Fetch app IDs by name
appIds, err := impl.getAppIdsByName(appNameUniqueIdentifier)
if err != nil {
impl.logger.Errorw("error in fetching app Ids by appIdentifier", "appNameUniqueIdentifier", appNameUniqueIdentifier, "err", err)
return nil, err
}

// Fetch apps by IDs from App table for duplicated entries
apps, err := impl.appRepository.FindByIds(appIds)
if err != nil || errors.Is(err, pg.ErrNoRows) {
impl.logger.Errorw("error in fetching app List by appIds", "appIds", appIds, "err", err)
return nil, err
}

// Identify the earliest and duplicated app entries
earliestApp, duplicatedApp := identifyDuplicateApps(apps)

// Fetch the installed app associated with the duplicated app
installedApp, err := impl.installedAppRepository.GetInstalledAppsByAppId(duplicatedApp.Id)
if err != nil {
impl.logger.Errorw("error in fetching installed app by appId", "appId", duplicatedApp.Id, "err", err)
return nil, err
}
// Update duplicated app entries
err = impl.installedAppDbService.UpdateDuplicatedEntriesInAppAndInstalledApps(earliestApp, duplicatedApp, &installedApp)
if err != nil {
impl.logger.Errorw("error in updating duplicated entries", "earliestApp", earliestApp, "duplicatedApp", duplicatedApp, "err", err)
return nil, err
}

impl.logger.Debug("Successfully resolved duplicate app entries", "earliestApp", earliestApp, "duplicatedApp", duplicatedApp)
return earliestApp, nil

}

// getAppIdsByName fetches app IDs by the app name unique identifier [for duplicated active app]
func (impl AppCrudOperationServiceImpl) getAppIdsByName(appNameUniqueIdentifier string) ([]*int, error) {
slice := []string{appNameUniqueIdentifier}
appIds, err := impl.appRepository.FindIdsByNames(slice)
if err != nil {
return nil, err
}

// Convert each element to a pointer and store in a slice of pointers
ids := make([]*int, len(appIds))
for i := range appIds {
ids[i] = &appIds[i]
}
return ids, nil
}

// getAppAndProjectForAppIdentifier, returns app db model for an app unique identifier or from display_name if both exists else it throws pg.ErrNoRows
func (impl AppCrudOperationServiceImpl) getAppAndProjectForAppIdentifier(appIdentifier *helmBean.AppIdentifier) (*appRepository.App, error) {
app := &appRepository.App{}
var err error
appNameUniqueIdentifier := appIdentifier.GetUniqueAppNameIdentifier()
app, err = impl.appRepository.FindAppAndProjectByAppName(appNameUniqueIdentifier)
if err != nil && err != pg.ErrNoRows {
if err != nil && !errors.Is(err, pg.ErrNoRows) && !errors.Is(err, pg.ErrMultiRows) {
impl.logger.Errorw("error in fetching app meta data by unique app identifier", "appNameUniqueIdentifier", appNameUniqueIdentifier, "err", err)
return app, err
}
Expand All @@ -475,6 +530,14 @@ func (impl AppCrudOperationServiceImpl) getAppAndProjectForAppIdentifier(appIden
return app, err
}
}
if errors.Is(err, pg.ErrMultiRows) {

app, err = impl.handleDuplicateAppEntries(appNameUniqueIdentifier)
if err != nil {
impl.logger.Errorw("error in handling Duplicate entries in the app", "appNameUniqueIdentifier", appNameUniqueIdentifier, "err", err)
return app, err
}
}
return app, nil
}

Expand Down Expand Up @@ -532,17 +595,17 @@ func (impl AppCrudOperationServiceImpl) GetHelmAppMetaInfo(appId string) (*bean.
return nil, err
}
// if app.DisplayName is empty then that app_name is not yet migrated to app name unique identifier
if app.Id > 0 && len(app.DisplayName) == 0 {
if app != nil && app.Id > 0 && len(app.DisplayName) == 0 {
err = impl.updateAppNameToUniqueAppIdentifierInApp(app, appIdDecoded)
if err != nil {
impl.logger.Errorw("GetHelmAppMetaInfo, error in migrating displayName and appName to unique identifier for external apps", "appIdentifier", appIdDecoded, "err", err)
//not returning from here as we need to show helm app metadata even if migration of app_name fails, then migration can happen on project update
}
}
if app.Id == 0 {
if app != nil && app.Id == 0 {
app.AppName = appIdDecoded.ReleaseName
}
if util2.IsExternalChartStoreApp(app.DisplayName) {
if app != nil && util2.IsExternalChartStoreApp(app.DisplayName) {
displayName = app.DisplayName
}

Expand All @@ -568,9 +631,14 @@ func (impl AppCrudOperationServiceImpl) GetHelmAppMetaInfo(appId string) (*bean.
displayName = InstalledApp.App.DisplayName
}
}
// Safeguard against nil app cases
if app == nil {
impl.logger.Errorw("no rows found for the requested app", "appId", appId, "error", err)
return nil, fmt.Errorf("no rows found for the requested app, %q", pg.ErrNoRows)
}

user, err := impl.userRepository.GetByIdIncludeDeleted(app.CreatedBy)
if err != nil && err != pg.ErrNoRows {
if err != nil && !errors.Is(err, pg.ErrNoRows) {
impl.logger.Errorw("error in fetching user for app meta info", "error", err)
return nil, err
}
Expand Down
23 changes: 22 additions & 1 deletion pkg/app/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@

package app

import "strings"
import (
appRepository "github.com/devtron-labs/devtron/internal/sql/repository/app"
"strings"
)

// LabelMatchingRegex is the official k8s label matching regex, pls refer https://github.com/kubernetes/apimachinery/blob/bfd2aff97e594f6aad77acbe2cbbe190acc93cbc/pkg/util/validation/validation.go#L167
const LabelMatchingRegex = "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$"
Expand All @@ -43,3 +46,21 @@ func sanitizeLabels(extraAppLabels map[string]string) map[string]string {
}
return extraAppLabels
}

// identifyDuplicateApps identifies the earliest created app and the most recent duplicate app.
func identifyDuplicateApps(apps []*appRepository.App) (earliestApp *appRepository.App, duplicatedApp *appRepository.App) {
if len(apps) == 0 {
return nil, nil
}
earliestApp = apps[0]
duplicatedApp = apps[0]
for _, app := range apps[1:] {
if app.AuditLog.CreatedOn.Before(earliestApp.AuditLog.CreatedOn) {
earliestApp = app
}
if app.AuditLog.CreatedOn.After(duplicatedApp.AuditLog.CreatedOn) {
duplicatedApp = app
}
}
return earliestApp, duplicatedApp
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ func (impl *AppStoreDeploymentDBServiceImpl) AppStoreDeployOperationDB(installRe
}
// setting additional env data required in appStoreBean.InstallAppVersionDTO
adapter.UpdateAdditionalEnvDetails(installRequest, environment)

impl.appStoreValidator.Validate(installRequest, environment)

// Stage 1: Create App in tx (Only if AppId is not set already)
Expand Down
54 changes: 54 additions & 0 deletions pkg/appStore/installedApp/service/EAMode/InstalledAppDBService.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type InstalledAppDBService interface {
GetReleaseInfo(appIdentifier *helmBean.AppIdentifier) (*appStoreBean.InstallAppVersionDTO, error)
IsExternalAppLinkedToChartStore(appId int) (bool, []*appStoreRepo.InstalledApps, error)
CreateNewAppEntryForAllInstalledApps(installedApps []*appStoreRepo.InstalledApps) error
UpdateDuplicatedEntriesInAppAndInstalledApps(earlyApp *app.App, duplicatedApp *app.App, installedApp *appStoreRepo.InstalledApps) error
}

type InstalledAppDBServiceImpl struct {
Expand Down Expand Up @@ -399,6 +400,17 @@ func (impl *InstalledAppDBServiceImpl) CreateNewAppEntryForAllInstalledApps(inst
// Rollback tx on error.
defer tx.Rollback()
for _, installedApp := range installedApps {

//check if there is any app from its appName is exits and active ...if yes then we will not insert any extra entry in the db
appMetadataByAppName, err := impl.AppRepository.FindActiveByName(installedApp.App.AppName)
if err != nil && !util.IsErrNoRows(err) {
impl.Logger.Errorw("error in fetching app by unique app identifier", "appNameUniqueIdentifier", installedApp.GetUniqueAppNameIdentifier(), "err", err)
return err
}
if appMetadataByAppName != nil && appMetadataByAppName.Id > 0 {
//app already exists for this unique identifier hence not creating new app entry for this as it will get modified after this function
continue
}
//check if for this unique identifier name an app already exists, if yes then continue
appMetadata, err := impl.AppRepository.FindActiveByName(installedApp.GetUniqueAppNameIdentifier())
if err != nil && !util.IsErrNoRows(err) {
Expand Down Expand Up @@ -437,3 +449,45 @@ func (impl *InstalledAppDBServiceImpl) CreateNewAppEntryForAllInstalledApps(inst
tx.Commit()
return nil
}

// UpdateDuplicatedEntriesInAppAndInstalledApps performs the updation in app table and installedApps table for the cases when multiple active app found [typically two due to migration], here we are updating the db with its previous value in the installedApps table and early created app id
func (impl *InstalledAppDBServiceImpl) UpdateDuplicatedEntriesInAppAndInstalledApps(earlyApp *app.App, duplicatedApp *app.App, installedApp *appStoreRepo.InstalledApps) error {
// db operations
dbConnection := impl.InstalledAppRepository.GetConnection()
tx, err := dbConnection.Begin()
if err != nil {
return err
}
// Rollback tx on error.
defer func(tx *pg.Tx) {
err := tx.Rollback()
if err != nil {
impl.Logger.Errorw("Rollback error", "err", err)
}
}(tx)

//updated the app table with active column as false for the duplicated app
duplicatedApp.Active = false
duplicatedApp.CreateAuditLog(bean3.SystemUserId)
err = impl.AppRepository.UpdateWithTxn(duplicatedApp, tx)
if err != nil {
impl.Logger.Errorw("error saving appModel", "err", err)
return err
}

// updating the installedApps table with its appId column with the previous app
installedApp.AppId = earlyApp.Id
installedApp.UpdateAuditLog(bean3.SystemUserId)
_, err = impl.InstalledAppRepository.UpdateInstalledApp(installedApp, tx)
if err != nil {
impl.Logger.Errorw("error saving updating installed app with new appId", "installedAppId", installedApp.Id, "err", err)
return err
}

err = tx.Commit()
if err != nil {
impl.Logger.Errorw("error saving appModel", "err", err)
return err
}
return nil
}
2 changes: 1 addition & 1 deletion wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f1aa1fc

Please sign in to comment.