diff --git a/Wire.go b/Wire.go index 34c07bb0051..beb2f680376 100644 --- a/Wire.go +++ b/Wire.go @@ -228,6 +228,8 @@ func InitializeApp() (*App, error) { pipeline.NewPipelineBuilderImpl, wire.Bind(new(pipeline.PipelineBuilder), new(*pipeline.PipelineBuilderImpl)), + pipeline.NewBuildPipelineSwitchServiceImpl, + wire.Bind(new(pipeline.BuildPipelineSwitchService), new(*pipeline.BuildPipelineSwitchServiceImpl)), pipeline.NewCiPipelineConfigServiceImpl, wire.Bind(new(pipeline.CiPipelineConfigService), new(*pipeline.CiPipelineConfigServiceImpl)), pipeline.NewCiMaterialConfigServiceImpl, diff --git a/api/bean/AppView.go b/api/bean/AppView.go index 1cd180f41a3..eae8e444ebf 100644 --- a/api/bean/AppView.go +++ b/api/bean/AppView.go @@ -161,6 +161,7 @@ type DeploymentDetailContainer struct { Deprecated bool `json:"deprecated"` K8sVersion string `json:"k8sVersion"` CiArtifactId int `json:"ciArtifactId"` + ParentArtifactId int `json:"parentArtifactId"` ClusterId int `json:"clusterId"` DeploymentAppType string `json:"deploymentAppType"` CiPipelineId int `json:"-"` diff --git a/api/restHandler/app/BuildPipelineRestHandler.go b/api/restHandler/app/BuildPipelineRestHandler.go index 78eda463e63..056a6e52fb4 100644 --- a/api/restHandler/app/BuildPipelineRestHandler.go +++ b/api/restHandler/app/BuildPipelineRestHandler.go @@ -1101,7 +1101,7 @@ func (handler PipelineConfigRestHandlerImpl) GetCIPipelineById(w http.ResponseWr if handler.appWorkflowService.CheckCdPipelineByCiPipelineId(pipelineId) { for _, envId := range environmentIds { envObject := handler.enforcerUtil.GetEnvRBACNameByCiPipelineIdAndEnvId(pipelineId, envId) - if ok := handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionUpdate, envObject); !ok { + if ok := handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionGet, envObject); !ok { common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), "Unauthorized User", http.StatusForbidden) return } diff --git a/api/restHandler/app/DeploymentPipelineRestHandler.go b/api/restHandler/app/DeploymentPipelineRestHandler.go index df262ba4b1f..a25dedfadb0 100644 --- a/api/restHandler/app/DeploymentPipelineRestHandler.go +++ b/api/restHandler/app/DeploymentPipelineRestHandler.go @@ -20,6 +20,7 @@ import ( resourceGroup2 "github.com/devtron-labs/devtron/pkg/resourceGroup" "github.com/devtron-labs/devtron/pkg/resourceQualifiers" "github.com/devtron-labs/devtron/pkg/user/casbin" + "github.com/devtron-labs/devtron/pkg/variables/models" util2 "github.com/devtron-labs/devtron/util" "github.com/go-pg/pg" "github.com/gorilla/mux" @@ -214,11 +215,13 @@ func (handler PipelineConfigRestHandlerImpl) CreateCdPipeline(w http.ResponseWri return } for _, deploymentPipeline := range cdPipeline.Pipelines { - object := handler.enforcerUtil.GetAppRBACByAppNameAndEnvId(app.AppName, deploymentPipeline.EnvironmentId) - handler.Logger.Debugw("Triggered Request By:", "object", object) - if ok := handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionCreate, object); !ok { - common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), "Unauthorized User", http.StatusForbidden) - return + if deploymentPipeline.EnvironmentId > 0 { + object := handler.enforcerUtil.GetAppRBACByAppNameAndEnvId(app.AppName, deploymentPipeline.EnvironmentId) + handler.Logger.Debugw("Triggered Request By:", "object", object) + if ok := handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionCreate, object); !ok { + common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), "Unauthorized User", http.StatusForbidden) + return + } } } //RBAC @@ -325,7 +328,12 @@ func (handler PipelineConfigRestHandlerImpl) PatchCdPipeline(w http.ResponseWrit createResp, err := handler.pipelineBuilder.PatchCdPipelines(&cdPipeline, ctx) if err != nil { handler.Logger.Errorw("service err, PatchCdPipeline", "err", err, "payload", cdPipeline) - common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + + if errors.As(err, &models.ValidationError{}) { + common.WriteJsonResp(w, err, nil, http.StatusPreconditionFailed) + } else { + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + } return } common.WriteJsonResp(w, err, createResp, http.StatusOK) diff --git a/api/restHandler/app/PipelineConfigRestHandler.go b/api/restHandler/app/PipelineConfigRestHandler.go index e020ceb95fa..6d04e241bbf 100644 --- a/api/restHandler/app/PipelineConfigRestHandler.go +++ b/api/restHandler/app/PipelineConfigRestHandler.go @@ -64,7 +64,7 @@ import ( ) type PipelineRestHandlerEnvConfig struct { - UseArtifactListApiV2 bool `env:"USE_ARTIFACT_LISTING_API_V2"` + UseArtifactListApiV2 bool `env:"USE_ARTIFACT_LISTING_API_V2" envDefault:"true"` } type DevtronAppRestHandler interface { diff --git a/internal/sql/repository/AppListingRepository.go b/internal/sql/repository/AppListingRepository.go index 8b5b5e935c7..2b4a9f84f66 100644 --- a/internal/sql/repository/AppListingRepository.go +++ b/internal/sql/repository/AppListingRepository.go @@ -401,6 +401,7 @@ func (impl AppListingRepositoryImpl) deploymentDetailsByAppIdAndEnvId(ctx contex " p.deployment_app_delete_request," + " cia.data_source," + " cia.id as ci_artifact_id," + + " cia.parent_ci_artifact as parent_artifact_id," + " cl.k8s_version," + " env.cluster_id," + " env.is_virtual_environment," + diff --git a/internal/sql/repository/CiArtifactRepository.go b/internal/sql/repository/CiArtifactRepository.go index 5a1318757f1..32dd10ea270 100644 --- a/internal/sql/repository/CiArtifactRepository.go +++ b/internal/sql/repository/CiArtifactRepository.go @@ -92,8 +92,9 @@ type CiArtifactRepository interface { GetLatestArtifactTimeByCiPipelineId(ciPipelineId int) (*CiArtifact, error) GetArtifactsByCDPipelineV2(cdPipelineId int) ([]CiArtifact, error) GetArtifactsByCDPipelineAndRunnerType(cdPipelineId int, runnerType bean.WorkflowType) ([]CiArtifact, error) - SaveAll(artifacts []*CiArtifact) error + SaveAll(artifacts []*CiArtifact) ([]*CiArtifact, error) GetArtifactsByCiPipelineId(ciPipelineId int) ([]CiArtifact, error) + GetArtifactsByCiPipelineIds(ciPipelineIds []int) ([]CiArtifact, error) FinDByParentCiArtifactAndCiId(parentCiArtifact int, ciPipelineIds []int) ([]*CiArtifact, error) GetLatest(cdPipelineId int) (int, error) GetByImageDigest(imageDigest string) (artifact *CiArtifact, err error) @@ -104,6 +105,8 @@ type CiArtifactRepository interface { FindArtifactByListFilter(listingFilterOptions *bean.ArtifactsListFilterOptions) ([]CiArtifact, int, error) GetArtifactsByDataSourceAndComponentId(dataSource string, componentId int) ([]CiArtifact, error) FindCiArtifactByImagePaths(images []string) ([]CiArtifact, error) + + UpdateLatestTimestamp(artifactIds []int) error } type CiArtifactRepositoryImpl struct { @@ -115,7 +118,7 @@ func NewCiArtifactRepositoryImpl(dbConnection *pg.DB, logger *zap.SugaredLogger) return &CiArtifactRepositoryImpl{dbConnection: dbConnection, logger: logger} } -func (impl CiArtifactRepositoryImpl) SaveAll(artifacts []*CiArtifact) error { +func (impl CiArtifactRepositoryImpl) SaveAll(artifacts []*CiArtifact) ([]*CiArtifact, error) { err := impl.dbConnection.RunInTransaction(func(tx *pg.Tx) error { for _, ciArtifact := range artifacts { r, err := tx.Model(ciArtifact).Insert() @@ -126,6 +129,18 @@ func (impl CiArtifactRepositoryImpl) SaveAll(artifacts []*CiArtifact) error { } return nil }) + return artifacts, err +} + +func (impl CiArtifactRepositoryImpl) UpdateLatestTimestamp(artifactIds []int) error { + if len(artifactIds) == 0 { + impl.logger.Debug("UpdateLatestTimestamp empty list of artifacts, not updating") + return nil + } + _, err := impl.dbConnection.Model(&CiArtifact{}). + Set("updated_on = ?", time.Now()). + Where("id IN (?)", pg.In(artifactIds)). + Update() return err } @@ -602,6 +617,24 @@ func (impl CiArtifactRepositoryImpl) GetArtifactsByCiPipelineId(ciPipelineId int return artifacts, err } +func (impl CiArtifactRepositoryImpl) GetArtifactsByCiPipelineIds(ciPipelineIds []int) ([]CiArtifact, error) { + var artifacts []CiArtifact + if len(ciPipelineIds) == 0 { + impl.logger.Debug("GetArtifactsByCiPipelineIds empty list of ids, returning empty list of artifacts") + return artifacts, nil + } + err := impl.dbConnection. + Model(&artifacts). + Column("ci_artifact.*"). + Join("INNER JOIN ci_pipeline cp on cp.id=ci_artifact.pipeline_id"). + Where("cp.id in (?)", pg.In(ciPipelineIds)). + Where("cp.deleted = ?", false). + Order("ci_artifact.id DESC"). + Select() + + return artifacts, err +} + func (impl CiArtifactRepositoryImpl) FinDByParentCiArtifactAndCiId(parentCiArtifact int, ciPipelineIds []int) ([]*CiArtifact, error) { var CiArtifacts []*CiArtifact err := impl.dbConnection. diff --git a/internal/sql/repository/appWorkflow/AppWorkflowRepository.go b/internal/sql/repository/appWorkflow/AppWorkflowRepository.go index ab9420aa9d2..0d6d5207fa2 100644 --- a/internal/sql/repository/appWorkflow/AppWorkflowRepository.go +++ b/internal/sql/repository/appWorkflow/AppWorkflowRepository.go @@ -61,6 +61,8 @@ type AppWorkflowRepository interface { FindByCDPipelineIds(cdPipelineIds []int) ([]*AppWorkflowMapping, error) FindByWorkflowIds(workflowIds []int) ([]*AppWorkflowMapping, error) FindMappingByAppIds(appIds []int) ([]*AppWorkflowMapping, error) + UpdateParentComponentDetails(tx *pg.Tx, oldComponentId int, oldComponentType string, newComponentId int, newComponentType string) error + FindWFMappingByComponent(componentType string, componentId int) (*AppWorkflowMapping, error) } type AppWorkflowRepositoryImpl struct { @@ -411,6 +413,15 @@ func (impl AppWorkflowRepositoryImpl) FindWFCDMappingByExternalCiId(externalCiId Select() return models, err } +func (impl AppWorkflowRepositoryImpl) FindWFMappingByComponent(componentType string, componentId int) (*AppWorkflowMapping, error) { + model := AppWorkflowMapping{} + err := impl.dbConnection.Model(&model). + Where("type = ?", componentType). + Where("component_id = ?", componentId). + Where("active = ?", true). + Select() + return &model, err +} func (impl AppWorkflowRepositoryImpl) FindWFCDMappingByExternalCiIdByIdsIn(externalCiId []int) ([]*AppWorkflowMapping, error) { var models []*AppWorkflowMapping @@ -461,3 +472,19 @@ func (impl AppWorkflowRepositoryImpl) FindMappingByAppIds(appIds []int) ([]*AppW Select() return appWorkflowsMapping, err } + +func (impl AppWorkflowRepositoryImpl) UpdateParentComponentDetails(tx *pg.Tx, oldParentId int, oldParentType string, newParentId int, newParentType string) error { + + /*updateQuery := fmt.Sprintf(" UPDATE app_workflow_mapping "+ + " SET parent_type = (select type from new_app_workflow_mapping),parent_id = (select id from new_app_workflow_mapping) where parent_id = %v and parent_type='%v' and active = true", oldComponentId, oldComponentType) + + finalQuery := withQuery + updateQuery*/ + _, err := tx.Model((*AppWorkflowMapping)(nil)). + Set("parent_type = ?", newParentType). + Set("parent_id = ?", newParentId). + Where("parent_type = ?", oldParentType). + Where("parent_id = ?", oldParentId). + Where("active = true"). + Update() + return err +} diff --git a/internal/sql/repository/pipelineConfig/CiPipelineRepository.go b/internal/sql/repository/pipelineConfig/CiPipelineRepository.go index 1792ea2937f..c0631f1217e 100644 --- a/internal/sql/repository/pipelineConfig/CiPipelineRepository.go +++ b/internal/sql/repository/pipelineConfig/CiPipelineRepository.go @@ -84,6 +84,7 @@ type CiPipelineScript struct { } type CiPipelineRepository interface { + sql.TransactionWrapper Save(pipeline *CiPipeline, tx *pg.Tx) error SaveCiEnvMapping(cienvmapping *CiEnvMapping, tx *pg.Tx) error SaveExternalCi(pipeline *ExternalCiPipeline, tx *pg.Tx) (*ExternalCiPipeline, error) @@ -112,6 +113,7 @@ type CiPipelineRepository interface { FindByName(pipelineName string) (pipeline *CiPipeline, err error) CheckIfPipelineExistsByNameAndAppId(pipelineName string, appId int) (bool, error) FindByParentCiPipelineId(parentCiPipelineId int) ([]*CiPipeline, error) + FindByParentIdAndType(parentCiPipelineId int, pipelineType string) ([]*CiPipeline, error) FetchParentCiPipelinesForDG() ([]*CiPipelinesMap, error) FetchCiPipelinesForDG(parentId int, childCiPipelineIds []int) (*CiPipeline, int, error) @@ -125,16 +127,19 @@ type CiPipelineRepository interface { FindAppIdsForCiPipelineIds(pipelineIds []int) (map[int]int, error) GetCiPipelineByArtifactId(artifactId int) (*CiPipeline, error) GetExternalCiPipelineByArtifactId(artifactId int) (*ExternalCiPipeline, error) + FindLinkedCiCount(ciPipelineId int) (int, error) } type CiPipelineRepositoryImpl struct { dbConnection *pg.DB logger *zap.SugaredLogger + *sql.TransactionUtilImpl } func NewCiPipelineRepositoryImpl(dbConnection *pg.DB, logger *zap.SugaredLogger) *CiPipelineRepositoryImpl { return &CiPipelineRepositoryImpl{ - dbConnection: dbConnection, - logger: logger, + dbConnection: dbConnection, + logger: logger, + TransactionUtilImpl: sql.NewTransactionUtilImpl(dbConnection), } } @@ -147,6 +152,16 @@ func (impl CiPipelineRepositoryImpl) FindByParentCiPipelineId(parentCiPipelineId return ciPipelines, err } +func (impl CiPipelineRepositoryImpl) FindByParentIdAndType(parentCiPipelineId int, pipelineType string) ([]*CiPipeline, error) { + var ciPipelines []*CiPipeline + err := impl.dbConnection.Model(&ciPipelines). + Where("parent_ci_pipeline = ?", parentCiPipelineId). + Where("ci_pipeline_type = ?", pipelineType). + Where("active = ?", true). + Select() + return ciPipelines, err +} + func (impl CiPipelineRepositoryImpl) FindByIdsIn(ids []int) ([]*CiPipeline, error) { var ciPipelines []*CiPipeline err := impl.dbConnection.Model(&ciPipelines). @@ -552,3 +567,15 @@ func (impl CiPipelineRepositoryImpl) FindCiPipelineByAppIdAndEnvIds(appId int, e _, err := impl.dbConnection.Query(&pipelines, query, appId, pg.In(envIds)) return pipelines, err } + +func (impl CiPipelineRepositoryImpl) FindLinkedCiCount(ciPipelineId int) (int, error) { + pipeline := &CiPipeline{} + cnt, err := impl.dbConnection.Model(pipeline). + Where("parent_ci_pipeline = ?", ciPipelineId). + Where("deleted = ?", false). + Count() + if err == pg.ErrNoRows { + return 0, nil + } + return cnt, err +} diff --git a/internal/sql/repository/pipelineConfig/CiWorkflowRepository.go b/internal/sql/repository/pipelineConfig/CiWorkflowRepository.go index 710a47a8e39..338032227a4 100644 --- a/internal/sql/repository/pipelineConfig/CiWorkflowRepository.go +++ b/internal/sql/repository/pipelineConfig/CiWorkflowRepository.go @@ -82,8 +82,12 @@ type CiWorkflow struct { CiPipeline *CiPipeline } -func (r *CiWorkflow) IsExternalRunInJobType() bool { - return r.EnvironmentId != 0 +func (ciWorkflow *CiWorkflow) InProgress() bool { + return ciWorkflow.Status == "Running" || ciWorkflow.Status == "Starting" +} + +func (ciWorkflow *CiWorkflow) IsExternalRunInJobType() bool { + return ciWorkflow.EnvironmentId != 0 } type WorkflowWithArtifact struct { diff --git a/internal/sql/repository/pipelineConfig/PipelineRepository.go b/internal/sql/repository/pipelineConfig/PipelineRepository.go index f4d8b3954dc..e59364034ba 100644 --- a/internal/sql/repository/pipelineConfig/PipelineRepository.go +++ b/internal/sql/repository/pipelineConfig/PipelineRepository.go @@ -28,6 +28,7 @@ import ( "github.com/devtron-labs/devtron/pkg/sql" "github.com/go-pg/pg" "go.uber.org/zap" + "k8s.io/utils/pointer" "strconv" "time" ) @@ -112,6 +113,7 @@ type PipelineRepository interface { FindAppAndEnvironmentAndProjectByPipelineIds(pipelineIds []int) (pipelines []*Pipeline, err error) FilterDeploymentDeleteRequestedPipelineIds(cdPipelineIds []int) (map[int]bool, error) FindDeploymentTypeByPipelineIds(cdPipelineIds []int) (map[int]DeploymentObject, error) + UpdateOldCiPipelineIdToNewCiPipelineId(tx *pg.Tx, oldCiPipelineId, newCiPipelineId int) error } type CiArtifactDTO struct { @@ -701,3 +703,14 @@ func (impl PipelineRepositoryImpl) FindDeploymentTypeByPipelineIds(cdPipelineIds return pipelineIdsMap, nil } + +func (impl PipelineRepositoryImpl) UpdateOldCiPipelineIdToNewCiPipelineId(tx *pg.Tx, oldCiPipelineId, newCiPipelineId int) error { + newCiPipId := pointer.Int(newCiPipelineId) + if newCiPipelineId == 0 { + newCiPipId = nil + } + _, err := tx.Model((*Pipeline)(nil)).Set("ci_pipeline_id = ?", newCiPipId). + Where("ci_pipeline_id = ? ", oldCiPipelineId). + Where("deleted = ?", false).Update() + return err +} diff --git a/pkg/appClone/AppCloneService.go b/pkg/appClone/AppCloneService.go index 18394b9fd37..952d5a62051 100644 --- a/pkg/appClone/AppCloneService.go +++ b/pkg/appClone/AppCloneService.go @@ -56,6 +56,7 @@ type AppCloneServiceImpl struct { ciPipelineRepository pipelineConfig.CiPipelineRepository pipelineRepository pipelineConfig.PipelineRepository appWorkflowRepository appWorkflow2.AppWorkflowRepository + ciPipelineConfigService pipeline.CiPipelineConfigService } func NewAppCloneServiceImpl(logger *zap.SugaredLogger, @@ -69,7 +70,8 @@ func NewAppCloneServiceImpl(logger *zap.SugaredLogger, ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository, pipelineStageService pipeline.PipelineStageService, ciTemplateService pipeline.CiTemplateService, appRepository app2.AppRepository, ciPipelineRepository pipelineConfig.CiPipelineRepository, - pipelineRepository pipelineConfig.PipelineRepository, appWorkflowRepository appWorkflow2.AppWorkflowRepository) *AppCloneServiceImpl { + pipelineRepository pipelineConfig.PipelineRepository, appWorkflowRepository appWorkflow2.AppWorkflowRepository, + ciPipelineConfigService pipeline.CiPipelineConfigService) *AppCloneServiceImpl { return &AppCloneServiceImpl{ logger: logger, pipelineBuilder: pipelineBuilder, @@ -85,6 +87,7 @@ func NewAppCloneServiceImpl(logger *zap.SugaredLogger, ciPipelineRepository: ciPipelineRepository, pipelineRepository: pipelineRepository, appWorkflowRepository: appWorkflowRepository, + ciPipelineConfigService: ciPipelineConfigService, } } @@ -104,6 +107,7 @@ type CreateWorkflowMappingDto struct { newWfId int gitMaterialMapping map[int]int externalCiPipelineId int + oldToNewCDPipelineId map[int]int } func (impl *AppCloneServiceImpl) CloneApp(createReq *bean.CreateAppDTO, context context.Context) (*bean.CreateAppDTO, error) { @@ -592,6 +596,9 @@ func (impl *AppCloneServiceImpl) CreateWf(oldAppId, newAppId int, userId int32, } impl.logger.Debugw("workflow found", "wf", refAppWFs) + createWorkflowMappingDtoResp := CreateWorkflowMappingDto{ + oldToNewCDPipelineId: make(map[int]int), + } for _, refAppWF := range refAppWFs { thisWf := appWorkflow.AppWorkflowDto{ Id: 0, @@ -614,10 +621,11 @@ func (impl *AppCloneServiceImpl) CreateWf(oldAppId, newAppId int, userId int32, } } createWorkflowMappingDto := CreateWorkflowMappingDto{ - newAppId: newAppId, - oldAppId: oldAppId, - newWfId: thisWf.Id, - userId: userId, + newAppId: newAppId, + oldAppId: oldAppId, + newWfId: thisWf.Id, + userId: userId, + oldToNewCDPipelineId: createWorkflowMappingDtoResp.oldToNewCDPipelineId, } var externalCiPipelineId int if isExternalCiPresent { @@ -630,7 +638,7 @@ func (impl *AppCloneServiceImpl) CreateWf(oldAppId, newAppId int, userId int32, createWorkflowMappingDto.gitMaterialMapping = gitMaterialMapping createWorkflowMappingDto.externalCiPipelineId = externalCiPipelineId - err = impl.createWfInstances(refAppWF.AppWorkflowMappingDto, createWorkflowMappingDto, ctx) + createWorkflowMappingDtoResp, err = impl.createWfInstances(refAppWF.AppWorkflowMappingDto, createWorkflowMappingDto, ctx) if err != nil { impl.logger.Errorw("error in creating workflow mapping", "err", err) return nil, err @@ -648,7 +656,7 @@ func (impl *AppCloneServiceImpl) createExternalCiAndAppWorkflowMapping(createWor } // Rollback tx on error. defer tx.Rollback() - externalCiPipelineId, err := impl.pipelineBuilder.CreateExternalCiAndAppWorkflowMapping(createWorkflowMappingDto.newAppId, createWorkflowMappingDto.newWfId, createWorkflowMappingDto.userId, tx) + externalCiPipelineId, _, err := impl.ciPipelineConfigService.CreateExternalCiAndAppWorkflowMapping(createWorkflowMappingDto.newAppId, createWorkflowMappingDto.newWfId, createWorkflowMappingDto.userId, tx) if err != nil { impl.logger.Errorw("error in creating new external ci pipeline and new app workflow mapping", "refAppId", createWorkflowMappingDto.oldAppId, "newAppId", createWorkflowMappingDto.newAppId, "err", err) return 0, err @@ -660,7 +668,7 @@ func (impl *AppCloneServiceImpl) createExternalCiAndAppWorkflowMapping(createWor return externalCiPipelineId, nil } -func (impl *AppCloneServiceImpl) createWfInstances(refWfMappings []appWorkflow.AppWorkflowMappingDto, createWorkflowMappingDto CreateWorkflowMappingDto, ctx context.Context) error { +func (impl *AppCloneServiceImpl) createWfInstances(refWfMappings []appWorkflow.AppWorkflowMappingDto, createWorkflowMappingDto CreateWorkflowMappingDto, ctx context.Context) (CreateWorkflowMappingDto, error) { impl.logger.Debugw("wf mapping cloning", "refWfMappings", refWfMappings) var ciMapping []appWorkflow.AppWorkflowMappingDto var cdMappings []appWorkflow.AppWorkflowMappingDto @@ -673,14 +681,14 @@ func (impl *AppCloneServiceImpl) createWfInstances(refWfMappings []appWorkflow.A } else if appWf.Type == appWorkflow2.WEBHOOK { webhookMappings = append(webhookMappings, appWf) } else { - return fmt.Errorf("unsupported wf type: %s", appWf.Type) + return createWorkflowMappingDto, fmt.Errorf("unsupported wf type: %s", appWf.Type) } } sourceToNewPipelineIdMapping := make(map[int]int) refApp, err := impl.pipelineBuilder.GetApp(createWorkflowMappingDto.oldAppId) if err != nil { impl.logger.Errorw("error in getting app from refAppId", "refAppId", createWorkflowMappingDto.oldAppId) - return err + return createWorkflowMappingDto, err } if len(webhookMappings) > 0 { for _, refwebhookMappings := range cdMappings { @@ -699,40 +707,41 @@ func (impl *AppCloneServiceImpl) createWfInstances(refWfMappings []appWorkflow.A impl.logger.Debugw("cd pipeline created", "pipeline", pipeline) if err != nil { impl.logger.Errorw("error in getting cd-pipeline", "refAppId", createWorkflowMappingDto.oldAppId, "newAppId", createWorkflowMappingDto.newAppId, "err", err) - return err + return createWorkflowMappingDto, err } } - return nil + return createWorkflowMappingDto, nil } if len(ciMapping) == 0 { impl.logger.Warn("no ci pipeline found") - return nil + return createWorkflowMappingDto, nil } else if len(ciMapping) != 1 { impl.logger.Warn("more than one ci pipeline not supported") - return nil + return createWorkflowMappingDto, nil } if err != nil { - return err + return createWorkflowMappingDto, err } var ci *bean.CiConfigRequest for _, refCiMapping := range ciMapping { impl.logger.Debugw("creating ci", "ref", refCiMapping) cloneCiPipelineRequest := &cloneCiPipelineRequest{ - refAppId: createWorkflowMappingDto.oldAppId, - refCiPipelineId: refCiMapping.ComponentId, - userId: createWorkflowMappingDto.userId, - appId: createWorkflowMappingDto.newAppId, - wfId: createWorkflowMappingDto.newWfId, - gitMaterialMapping: createWorkflowMappingDto.gitMaterialMapping, - refAppName: refApp.AppName, + refAppId: createWorkflowMappingDto.oldAppId, + refCiPipelineId: refCiMapping.ComponentId, + userId: createWorkflowMappingDto.userId, + appId: createWorkflowMappingDto.newAppId, + wfId: createWorkflowMappingDto.newWfId, + gitMaterialMapping: createWorkflowMappingDto.gitMaterialMapping, + refAppName: refApp.AppName, + oldToNewIdForLinkedCD: createWorkflowMappingDto.oldToNewCDPipelineId, } ci, err = impl.CreateCiPipeline(cloneCiPipelineRequest) if err != nil { impl.logger.Errorw("error in creating ci pipeline, app clone", "err", err) - return err + return createWorkflowMappingDto, err } impl.logger.Debugw("ci created", "ci", ci) } @@ -751,8 +760,9 @@ func (impl *AppCloneServiceImpl) createWfInstances(refWfMappings []appWorkflow.A pipeline, err := impl.CreateCdPipeline(cdCloneReq, ctx) if err != nil { impl.logger.Errorw("error in creating cd pipeline, app clone", "err", err) - return err + return createWorkflowMappingDto, err } + createWorkflowMappingDto.oldToNewCDPipelineId[refCdMapping.ComponentId] = pipeline.Pipelines[0].Id impl.logger.Debugw("cd pipeline created", "pipeline", pipeline) } @@ -761,17 +771,18 @@ func (impl *AppCloneServiceImpl) createWfInstances(refWfMappings []appWorkflow.A //find cd //save cd //save mappings - return nil + return createWorkflowMappingDto, nil } type cloneCiPipelineRequest struct { - refAppId int - refCiPipelineId int - userId int32 - appId int - wfId int - gitMaterialMapping map[int]int - refAppName string + refAppId int + refCiPipelineId int + userId int32 + appId int + wfId int + gitMaterialMapping map[int]int + refAppName string + oldToNewIdForLinkedCD map[int]int } func (impl *AppCloneServiceImpl) CreateCiPipeline(req *cloneCiPipelineRequest) (*bean.CiConfigRequest, error) { diff --git a/pkg/bean/app.go b/pkg/bean/app.go index 41b44a06280..2ca21eac976 100644 --- a/pkg/bean/app.go +++ b/pkg/bean/app.go @@ -20,6 +20,8 @@ package bean import ( "encoding/json" bean2 "github.com/devtron-labs/devtron/api/bean" + repository3 "github.com/devtron-labs/devtron/internal/sql/repository" + "github.com/devtron-labs/devtron/internal/sql/repository/appWorkflow" "github.com/devtron-labs/devtron/internal/sql/repository/helper" repository2 "github.com/devtron-labs/devtron/internal/sql/repository/imageTagging" "github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig" @@ -185,10 +187,10 @@ const ( ) const ( - NORMAL PipelineType = "NORMAL" - LINKED PipelineType = "LINKED" - EXTERNAL PipelineType = "EXTERNAL" - CI_JOB PipelineType = "CI_JOB" + NORMAL PipelineType = "NORMAL" + LINKED PipelineType = "LINKED" + EXTERNAL PipelineType = "EXTERNAL" + CI_JOB PipelineType = "CI_JOB" ) const ( @@ -270,13 +272,24 @@ type CiMaterialPatchResponse struct { } type CiPatchRequest struct { - CiPipeline *CiPipeline `json:"ciPipeline"` - AppId int `json:"appId,omitempty"` - Action PatchAction `json:"action"` - AppWorkflowId int `json:"appWorkflowId,omitempty"` - UserId int32 `json:"-"` - IsJob bool `json:"-"` - IsCloneJob bool `json:"isCloneJob,omitempty"` + CiPipeline *CiPipeline `json:"ciPipeline"` + AppId int `json:"appId,omitempty"` + Action PatchAction `json:"action"` + AppWorkflowId int `json:"appWorkflowId,omitempty"` + UserId int32 `json:"-"` + IsJob bool `json:"-"` + IsCloneJob bool `json:"isCloneJob,omitempty"` + + ParentCDPipeline int `json:"parentCDPipeline"` + DeployEnvId int `json:"deployEnvId"` + SwitchFromCiPipelineId int `json:"switchFromCiPipelineId"` + SwitchFromExternalCiPipelineId int `json:"switchFromExternalCiPipelineId"` + SwitchFromCiPipelineType PipelineType `json:"-"` + SwitchToCiPipelineType PipelineType `json:"-"` +} + +func (ciPatchRequest CiPatchRequest) IsSwitchCiPipelineRequest() bool { + return (ciPatchRequest.SwitchFromCiPipelineId != 0 || ciPatchRequest.SwitchFromExternalCiPipelineId != 0) } type CiRegexPatchRequest struct { @@ -331,29 +344,31 @@ type TriggerViewCiConfig struct { } type CiConfigRequest struct { - Id int `json:"id,omitempty" validate:"number"` //ciTemplateId - AppId int `json:"appId,omitempty" validate:"required,number"` - DockerRegistry string `json:"dockerRegistry,omitempty" ` //repo id example ecr mapped one-one with gocd registry entry - DockerRepository string `json:"dockerRepository,omitempty"` // example test-app-1 which is inside ecr - CiBuildConfig *bean.CiBuildConfigBean `json:"ciBuildConfig"` - CiPipelines []*CiPipeline `json:"ciPipelines,omitempty" validate:"dive"` //a pipeline will be built for each ciMaterial - AppName string `json:"appName,omitempty"` - Version string `json:"version,omitempty"` //gocd etag used for edit purpose - DockerRegistryUrl string `json:"-"` - CiTemplateName string `json:"-"` - UserId int32 `json:"-"` - Materials []Material `json:"materials"` - AppWorkflowId int `json:"appWorkflowId,omitempty"` - BeforeDockerBuild []*Task `json:"beforeDockerBuild,omitempty" validate:"dive"` - AfterDockerBuild []*Task `json:"afterDockerBuild,omitempty" validate:"dive"` - ScanEnabled bool `json:"scanEnabled,notnull"` - CreatedOn time.Time `sql:"created_on,type:timestamptz"` - CreatedBy int32 `sql:"created_by,type:integer"` - UpdatedOn time.Time `sql:"updated_on,type:timestamptz"` - UpdatedBy int32 `sql:"updated_by,type:integer"` - IsJob bool `json:"-"` - CiGitMaterialId int `json:"ciGitConfiguredId"` - IsCloneJob bool `json:"isCloneJob,omitempty"` + Id int `json:"id,omitempty" validate:"number"` //ciTemplateId + AppId int `json:"appId,omitempty" validate:"required,number"` + DockerRegistry string `json:"dockerRegistry,omitempty" ` //repo id example ecr mapped one-one with gocd registry entry + DockerRepository string `json:"dockerRepository,omitempty"` // example test-app-1 which is inside ecr + CiBuildConfig *bean.CiBuildConfigBean `json:"ciBuildConfig"` + CiPipelines []*CiPipeline `json:"ciPipelines,omitempty" validate:"dive"` //a pipeline will be built for each ciMaterial + AppName string `json:"appName,omitempty"` + Version string `json:"version,omitempty"` //gocd etag used for edit purpose + DockerRegistryUrl string `json:"-"` + CiTemplateName string `json:"-"` + UserId int32 `json:"-"` + Materials []Material `json:"materials"` + AppWorkflowId int `json:"appWorkflowId,omitempty"` + BeforeDockerBuild []*Task `json:"beforeDockerBuild,omitempty" validate:"dive"` + AfterDockerBuild []*Task `json:"afterDockerBuild,omitempty" validate:"dive"` + ScanEnabled bool `json:"scanEnabled,notnull"` + CreatedOn time.Time `sql:"created_on,type:timestamptz"` + CreatedBy int32 `sql:"created_by,type:integer"` + UpdatedOn time.Time `sql:"updated_on,type:timestamptz"` + UpdatedBy int32 `sql:"updated_by,type:integer"` + IsJob bool `json:"-"` + CiGitMaterialId int `json:"ciGitConfiguredId"` + IsCloneJob bool `json:"isCloneJob,omitempty"` + AppWorkflowMapping *appWorkflow.AppWorkflowMapping `json:"-"` + Artifact *repository3.CiArtifact `json:"-"` } type CiPipelineMinResponse struct { @@ -569,6 +584,11 @@ type CDPipelineConfigObject struct { CustomTagObject *CustomTagData `json:"customTag"` CustomTagStage *repository.PipelineStageType `json:"customTagStage"` EnableCustomTag bool `json:"enableCustomTag"` + SwitchFromCiPipelineId int `json:"switchFromCiPipelineId"` +} + +func (cdpipelineConfig *CDPipelineConfigObject) IsSwitchCiPipelineRequest() bool { + return cdpipelineConfig.SwitchFromCiPipelineId > 0 && cdpipelineConfig.AppWorkflowId > 0 } type PreStageConfigMapSecretNames struct { diff --git a/pkg/pipeline/BuildPipelineConfigService.go b/pkg/pipeline/BuildPipelineConfigService.go index c67e2ec6419..12947972dbf 100644 --- a/pkg/pipeline/BuildPipelineConfigService.go +++ b/pkg/pipeline/BuildPipelineConfigService.go @@ -33,7 +33,6 @@ import ( "github.com/devtron-labs/devtron/pkg/pipeline/types" resourceGroup2 "github.com/devtron-labs/devtron/pkg/resourceGroup" "github.com/devtron-labs/devtron/pkg/sql" - util2 "github.com/devtron-labs/devtron/util" "github.com/devtron-labs/devtron/util/rbac" "github.com/go-pg/pg" "github.com/juju/errors" @@ -103,30 +102,35 @@ type CiPipelineConfigService interface { //GetExternalCiByEnvironment : lists externalCi for given environmentId and appIds GetExternalCiByEnvironment(request resourceGroup2.ResourceGroupingRequest) (ciConfig []*bean.ExternalCiConfig, err error) DeleteCiPipeline(request *bean.CiPatchRequest) (*bean.CiPipeline, error) + CreateExternalCiAndAppWorkflowMapping(appId, appWorkflowId int, userId int32, tx *pg.Tx) (int, *appWorkflow.AppWorkflowMapping, error) } type CiPipelineConfigServiceImpl struct { - logger *zap.SugaredLogger - ciTemplateService CiTemplateService - materialRepo pipelineConfig.MaterialRepository - ciPipelineRepository pipelineConfig.CiPipelineRepository - ciConfig *types.CiCdConfig - attributesService attributes.AttributesService - ciWorkflowRepository pipelineConfig.CiWorkflowRepository - appWorkflowRepository appWorkflow.AppWorkflowRepository - pipelineStageService PipelineStageService - pipelineRepository pipelineConfig.PipelineRepository - appRepo app2.AppRepository - dockerArtifactStoreRepository dockerRegistryRepository.DockerArtifactStoreRepository - ciCdPipelineOrchestrator CiCdPipelineOrchestrator - ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository - CiTemplateHistoryService history.CiTemplateHistoryService - securityConfig *SecurityConfig - ecrConfig *EcrConfig - ciPipelineMaterialRepository pipelineConfig.CiPipelineMaterialRepository - resourceGroupService resourceGroup2.ResourceGroupService - enforcerUtil rbac.EnforcerUtil - customTagService CustomTagService + logger *zap.SugaredLogger + ciTemplateService CiTemplateService + materialRepo pipelineConfig.MaterialRepository + ciPipelineRepository pipelineConfig.CiPipelineRepository + ciConfig *types.CiCdConfig + attributesService attributes.AttributesService + ciWorkflowRepository pipelineConfig.CiWorkflowRepository + appWorkflowRepository appWorkflow.AppWorkflowRepository + pipelineStageService PipelineStageService + pipelineRepository pipelineConfig.PipelineRepository + appRepo app2.AppRepository + dockerArtifactStoreRepository dockerRegistryRepository.DockerArtifactStoreRepository + ciCdPipelineOrchestrator CiCdPipelineOrchestrator + ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository + CiTemplateHistoryService history.CiTemplateHistoryService + securityConfig *SecurityConfig + ecrConfig *EcrConfig + ciPipelineMaterialRepository pipelineConfig.CiPipelineMaterialRepository + resourceGroupService resourceGroup2.ResourceGroupService + enforcerUtil rbac.EnforcerUtil + customTagService CustomTagService + deployedConfigurationHistoryService history.DeployedConfigurationHistoryService + ciPipelineHistoryService history.CiPipelineHistoryService + cdWorkflowRepository pipelineConfig.CdWorkflowRepository + buildPipelineSwitchService BuildPipelineSwitchService } func NewCiPipelineConfigServiceImpl(logger *zap.SugaredLogger, @@ -148,7 +152,11 @@ func NewCiPipelineConfigServiceImpl(logger *zap.SugaredLogger, enforcerUtil rbac.EnforcerUtil, ciWorkflowRepository pipelineConfig.CiWorkflowRepository, resourceGroupService resourceGroup2.ResourceGroupService, - customTagService CustomTagService) *CiPipelineConfigServiceImpl { + customTagService CustomTagService, + ciPipelineHistoryService history.CiPipelineHistoryService, + cdWorkflowRepository pipelineConfig.CdWorkflowRepository, + buildPipelineSwitchService BuildPipelineSwitchService, +) *CiPipelineConfigServiceImpl { securityConfig := &SecurityConfig{} err := env.Parse(securityConfig) @@ -177,6 +185,9 @@ func NewCiPipelineConfigServiceImpl(logger *zap.SugaredLogger, resourceGroupService: resourceGroupService, securityConfig: securityConfig, customTagService: customTagService, + ciPipelineHistoryService: ciPipelineHistoryService, + cdWorkflowRepository: cdWorkflowRepository, + buildPipelineSwitchService: buildPipelineSwitchService, } } @@ -292,58 +303,6 @@ func (impl *CiPipelineConfigServiceImpl) patchCiPipelineUpdateSource(baseCiConfi } -func (impl *CiPipelineConfigServiceImpl) addpipelineToTemplate(createRequest *bean.CiConfigRequest) (resp *bean.CiConfigRequest, err error) { - - if createRequest.AppWorkflowId == 0 { - // create workflow - wf := &appWorkflow.AppWorkflow{ - Name: fmt.Sprintf("wf-%d-%s", createRequest.AppId, util2.Generate(4)), - AppId: createRequest.AppId, - Active: true, - AuditLog: sql.AuditLog{ - CreatedOn: time.Now(), - UpdatedOn: time.Now(), - CreatedBy: createRequest.UserId, - UpdatedBy: createRequest.UserId, - }, - } - savedAppWf, err := impl.appWorkflowRepository.SaveAppWorkflow(wf) - if err != nil { - impl.logger.Errorw("err", err) - return nil, err - } - // workflow creation ends - createRequest.AppWorkflowId = savedAppWf.Id - } - //single ci in same wf validation - workflowMapping, err := impl.appWorkflowRepository.FindWFCIMappingByWorkflowId(createRequest.AppWorkflowId) - if err != nil && err != pg.ErrNoRows { - impl.logger.Errorw("error in fetching workflow mapping for ci validation", "err", err) - return nil, err - } - if len(workflowMapping) > 0 { - return nil, &util.ApiError{ - InternalMessage: "pipeline already exists", - UserDetailMessage: fmt.Sprintf("pipeline already exists in workflow"), - UserMessage: fmt.Sprintf("pipeline already exists in workflow")} - } - - //pipeline name validation - var pipelineNames []string - for _, pipeline := range createRequest.CiPipelines { - pipelineNames = append(pipelineNames, pipeline.Name) - } - if err != nil { - impl.logger.Errorw("error in creating pipeline group", "err", err) - return nil, err - } - createRequest, err = impl.ciCdPipelineOrchestrator.CreateCiConf(createRequest, createRequest.Id) - if err != nil { - return nil, err - } - return createRequest, err -} - func (impl *CiPipelineConfigServiceImpl) buildResponses() []bean.ResponseSchemaObject { responseSchemaObjects := make([]bean.ResponseSchemaObject, 0) schema := make(map[string]interface{}) @@ -1267,6 +1226,34 @@ func (impl *CiPipelineConfigServiceImpl) UpdateCiTemplate(updateRequest *bean.Ci return originalCiConf, nil } +func (impl *CiPipelineConfigServiceImpl) handlePipelineCreate(request *bean.CiPatchRequest, ciConfig *bean.CiConfigRequest) (*bean.CiConfigRequest, error) { + + pipelineExists, err := impl.ciPipelineRepository.CheckIfPipelineExistsByNameAndAppId(request.CiPipeline.Name, request.AppId) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error in fetching pipeline by name, FindByName", "err", err, "patch cipipeline name", request.CiPipeline.Name) + return nil, err + } + + if pipelineExists { + impl.logger.Errorw("pipeline name already exist", "err", err, "patch cipipeline name", request.CiPipeline.Name) + return nil, fmt.Errorf("pipeline name already exist") + } + + if request.IsSwitchCiPipelineRequest() { + impl.logger.Debugw("handling switch ci pipeline", "switchFromCiPipelineId", request.SwitchFromCiPipelineId, "switchFromExternalCiPipelineId", request.SwitchFromExternalCiPipelineId) + return impl.buildPipelineSwitchService.SwitchToCiPipelineExceptExternal(request, ciConfig) + } + + ciConfig.CiPipelines = []*bean.CiPipeline{request.CiPipeline} //request.CiPipeline + res, err := impl.ciCdPipelineOrchestrator.AddPipelineToTemplate(ciConfig, false) + if err != nil { + impl.logger.Errorw("error in adding pipeline to template", "ciConf", ciConfig, "err", err) + return nil, err + } + return res, nil + +} + func (impl *CiPipelineConfigServiceImpl) PatchCiPipeline(request *bean.CiPatchRequest) (ciConfig *bean.CiConfigRequest, err error) { ciConfig, err = impl.getCiTemplateVariables(request.AppId) if err != nil { @@ -1300,29 +1287,13 @@ func (impl *CiPipelineConfigServiceImpl) PatchCiPipeline(request *bean.CiPatchRe ciConfig.IsJob = request.IsJob // Check for clone job to not create env override again ciConfig.IsCloneJob = request.IsCloneJob - switch request.Action { case bean.CREATE: - impl.logger.Debugw("create patch request") - ciConfig.CiPipelines = []*bean.CiPipeline{request.CiPipeline} //request.CiPipeline - - pipelineExists, err := impl.ciPipelineRepository.CheckIfPipelineExistsByNameAndAppId(request.CiPipeline.Name, request.AppId) - if err != nil && err != pg.ErrNoRows { - impl.logger.Errorw("error in fetching pipeline by name, FindByName", "err", err, "patch cipipeline name", request.CiPipeline.Name) - return nil, err - } - - if pipelineExists { - impl.logger.Errorw("pipeline name already exist", "err", err, "patch cipipeline name", request.CiPipeline.Name) - return nil, fmt.Errorf("pipeline name already exist") - } - - res, err := impl.addpipelineToTemplate(ciConfig) + res, err := impl.handlePipelineCreate(request, ciConfig) if err != nil { - impl.logger.Errorw("error in adding pipeline to template", "ciConf", ciConfig, "err", err) - return nil, err + impl.logger.Errorw("error in creating ci pipeline", "err", err, "request", request, "ciConfig", ciConfig) } - return res, nil + return res, err case bean.UPDATE_SOURCE: return impl.patchCiPipelineUpdateSource(ciConfig, request.CiPipeline) case bean.DELETE: @@ -1438,7 +1409,7 @@ func (impl *CiPipelineConfigServiceImpl) CreateCiPipeline(createRequest *bean.Ci createRequest.Id = ciTemplate.Id createRequest.CiTemplateName = ciTemplate.TemplateName if len(createRequest.CiPipelines) > 0 { - conf, err := impl.addpipelineToTemplate(createRequest) + conf, err := impl.ciCdPipelineOrchestrator.AddPipelineToTemplate(createRequest, false) if err != nil { impl.logger.Errorw("error in pipeline creation ", "err", err) return nil, err @@ -1465,7 +1436,7 @@ func (impl *CiPipelineConfigServiceImpl) GetCiPipelineMin(appId int, envIds []in } if err == pg.ErrNoRows || len(pipelines) == 0 { impl.logger.Errorw("no ci pipeline found", "appId", appId, "err", err) - err = &util.ApiError{Code: "404", HttpStatusCode: 200, UserMessage: "no ci pipeline found"} + err = &util.ApiError{Code: "404", HttpStatusCode: 404, UserMessage: "no ci pipeline found"} return nil, err } parentCiPipelines, linkedCiPipelineIds, err := impl.ciPipelineRepository.FindParentCiPipelineMapByAppId(appId) @@ -2020,9 +1991,9 @@ func (impl *CiPipelineConfigServiceImpl) DeleteCiPipeline(request *bean.CiPatchR } if len(workflowMapping) > 0 { return nil, &util.ApiError{ - InternalMessage: "cd pipeline exists for this CI", - UserDetailMessage: fmt.Sprintf("cd pipeline exists for this CI"), - UserMessage: fmt.Sprintf("cd pipeline exists for this CI")} + InternalMessage: "Please delete deployment pipelines for this workflow first and try again.", + UserDetailMessage: fmt.Sprintf("Please delete deployment pipelines for this workflow first and try again."), + UserMessage: fmt.Sprintf("Please delete deployment pipelines for this workflow first and try again.")} } pipeline, err := impl.ciPipelineRepository.FindById(ciPipelineId) @@ -2085,3 +2056,30 @@ func (impl *CiPipelineConfigServiceImpl) DeleteCiPipeline(request *bean.CiPatchR //delete scm } + +func (impl *CiPipelineConfigServiceImpl) CreateExternalCiAndAppWorkflowMapping(appId, appWorkflowId int, userId int32, tx *pg.Tx) (int, *appWorkflow.AppWorkflowMapping, error) { + externalCiPipeline := &pipelineConfig.ExternalCiPipeline{ + AppId: appId, + AccessToken: "", + Active: true, + AuditLog: sql.AuditLog{CreatedBy: userId, CreatedOn: time.Now(), UpdatedOn: time.Now(), UpdatedBy: userId}, + } + externalCiPipeline, err := impl.ciPipelineRepository.SaveExternalCi(externalCiPipeline, tx) + if err != nil { + impl.logger.Errorw("error in saving external ci", "appId", appId, "err", err) + return 0, nil, err + } + appWorkflowMap := &appWorkflow.AppWorkflowMapping{ + AppWorkflowId: appWorkflowId, + ComponentId: externalCiPipeline.Id, + Type: "WEBHOOK", + Active: true, + AuditLog: sql.AuditLog{CreatedBy: userId, CreatedOn: time.Now(), UpdatedOn: time.Now(), UpdatedBy: userId}, + } + appWorkflowMap, err = impl.appWorkflowRepository.SaveAppWorkflowMapping(appWorkflowMap, tx) + if err != nil { + impl.logger.Errorw("error in saving app workflow mapping for external ci", "appId", appId, "appWorkflowId", appWorkflowId, "externalCiPipelineId", externalCiPipeline.Id, "err", err) + return 0, nil, err + } + return externalCiPipeline.Id, appWorkflowMap, nil +} diff --git a/pkg/pipeline/BuildPipelineSwitchService.go b/pkg/pipeline/BuildPipelineSwitchService.go new file mode 100644 index 00000000000..d5535446fc1 --- /dev/null +++ b/pkg/pipeline/BuildPipelineSwitchService.go @@ -0,0 +1,349 @@ +package pipeline + +import ( + "github.com/devtron-labs/devtron/internal/sql/repository/appWorkflow" + "github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig" + "github.com/devtron-labs/devtron/pkg/bean" + bean3 "github.com/devtron-labs/devtron/pkg/pipeline/bean" + "github.com/devtron-labs/devtron/pkg/pipeline/history" + repository4 "github.com/devtron-labs/devtron/pkg/pipeline/history/repository" + "github.com/devtron-labs/devtron/pkg/sql" + "github.com/go-pg/pg" + "github.com/juju/errors" + "go.uber.org/zap" + "time" +) + +type SwitchBuildPipelineValidationError string + +const ( + cannotConvertToSameType SwitchBuildPipelineValidationError = "cannot convert this pipeline to same type" + cannotConvertToExternalCi SwitchBuildPipelineValidationError = "current ci-pipeline cannot be converted to external-webhook type" + cannotConvertIfLinkedCiFound SwitchBuildPipelineValidationError = "cannot convert this ci-pipeline as it contains some linked ci-pipeline's" + cannotConvertIfLatestWorkflowIsInNonTerminalState SwitchBuildPipelineValidationError = "cannot convert this ci-pipeline as recent build of this ci-pipeline is in progressing state" +) + +type BuildPipelineSwitchService interface { + SwitchToExternalCi(tx *pg.Tx, appWorkflowMapping *appWorkflow.AppWorkflowMapping, switchFromCiPipelineId int, userId int32) error + SwitchToCiPipelineExceptExternal(request *bean.CiPatchRequest, ciConfig *bean.CiConfigRequest) (*bean.CiConfigRequest, error) +} + +type BuildPipelineSwitchServiceImpl struct { + logger *zap.SugaredLogger + ciPipelineRepository pipelineConfig.CiPipelineRepository + ciCdPipelineOrchestrator CiCdPipelineOrchestrator + pipelineRepository pipelineConfig.PipelineRepository + ciWorkflowRepository pipelineConfig.CiWorkflowRepository + appWorkflowRepository appWorkflow.AppWorkflowRepository + ciPipelineHistoryService history.CiPipelineHistoryService + ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository + ciPipelineMaterialRepository pipelineConfig.CiPipelineMaterialRepository +} + +func NewBuildPipelineSwitchServiceImpl(logger *zap.SugaredLogger, + ciPipelineRepository pipelineConfig.CiPipelineRepository, + ciCdPipelineOrchestrator CiCdPipelineOrchestrator, + pipelineRepository pipelineConfig.PipelineRepository, + ciWorkflowRepository pipelineConfig.CiWorkflowRepository, + appWorkflowRepository appWorkflow.AppWorkflowRepository, + ciPipelineHistoryService history.CiPipelineHistoryService, + ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository, + ciPipelineMaterialRepository pipelineConfig.CiPipelineMaterialRepository) *BuildPipelineSwitchServiceImpl { + return &BuildPipelineSwitchServiceImpl{ + logger: logger, + ciPipelineRepository: ciPipelineRepository, + ciCdPipelineOrchestrator: ciCdPipelineOrchestrator, + pipelineRepository: pipelineRepository, + ciWorkflowRepository: ciWorkflowRepository, + appWorkflowRepository: appWorkflowRepository, + ciPipelineHistoryService: ciPipelineHistoryService, + ciTemplateOverrideRepository: ciTemplateOverrideRepository, + ciPipelineMaterialRepository: ciPipelineMaterialRepository, + } +} + +func (impl *BuildPipelineSwitchServiceImpl) SwitchToExternalCi(tx *pg.Tx, appWorkflowMapping *appWorkflow.AppWorkflowMapping, switchFromCiPipelineId int, userId int32) error { + + err := impl.deleteCiAndItsWorkflowMappings(tx, switchFromCiPipelineId, userId) + if err != nil { + impl.logger.Errorw("error in deleting old ci-pipeline and getting the appWorkflow mapping of that", "err", err, "userId", userId) + return err + } + oldWorkflowMapping, err := impl.deleteAndGetAppWorkflowMappings(tx, appWorkflow.CIPIPELINE, switchFromCiPipelineId, userId) + if err != nil { + impl.logger.Errorw("error in deleting workflow mapping", "switchFromCiPipelineId", switchFromCiPipelineId, "err", err) + return err + } + err = impl.updateLinkedAppWorkflowMappings(tx, oldWorkflowMapping, appWorkflowMapping) + if err != nil { + impl.logger.Errorw("error in updating linked app-workflow-mappings ", "oldAppWorkflowMappingId", oldWorkflowMapping.Id, "currentAppWorkflowMapId", appWorkflowMapping.Id, "err", err, "userId", userId) + return err + } + + //setting new ci_pipeline_id to 0 because we dont store ci_pipeline_id if the ci_pipeline is external/webhook type. + err = impl.pipelineRepository.UpdateOldCiPipelineIdToNewCiPipelineId(tx, switchFromCiPipelineId, 0) + if err != nil { + impl.logger.Errorw("error in updating pipelines ci_pipeline_ids with new ci_pipelineId", "oldCiPipelineId", switchFromCiPipelineId) + return err + } + + return nil +} + +func (impl *BuildPipelineSwitchServiceImpl) SwitchToCiPipelineExceptExternal(request *bean.CiPatchRequest, ciConfig *bean.CiConfigRequest) (*bean.CiConfigRequest, error) { + if request.SwitchFromCiPipelineId != 0 && request.SwitchFromExternalCiPipelineId != 0 { + return nil, errors.New("invalid request payload, both switchFromCiPipelineId and switchFromExternalCiPipelineId cannot be set in the payload") + } + + //get the ciPipeline + var switchFromType bean.PipelineType + var switchFromPipelineId int + if request.SwitchFromExternalCiPipelineId != 0 { + switchFromType = bean.EXTERNAL + switchFromPipelineId = request.SwitchFromExternalCiPipelineId + } else { + switchFromPipelineId = request.SwitchFromCiPipelineId + switchFromType = request.SwitchFromCiPipelineType + } + + //validate switch request + err := impl.validateCiPipelineSwitch(switchFromPipelineId, request.CiPipeline.PipelineType, switchFromType) + if err != nil { + impl.logger.Errorw("validating failed for ci-pipeline switch request", "switchFromPipelineId", "switchFromType", switchFromType, "pipelineType", request.CiPipeline.PipelineType, "err", err) + return nil, err + } + + //delete old pipeline and it's appworkflow mapping + return impl.createNewPipelineAndReplaceOldPipelineLinks(request.CiPipeline, ciConfig, switchFromPipelineId, switchFromType, request.UserId) +} + +func (impl *BuildPipelineSwitchServiceImpl) createNewPipelineAndReplaceOldPipelineLinks(ciPipelineReq *bean.CiPipeline, ciConfig *bean.CiConfigRequest, switchFromPipelineId int, switchFromType bean.PipelineType, userId int32) (*bean.CiConfigRequest, error) { + tx, err := impl.ciPipelineRepository.StartTx() + if err != nil { + impl.logger.Errorw("error in starting transaction", "switchFromPipelineId", switchFromPipelineId, "switchFromType", switchFromType, "userId", userId, "err", err) + return nil, err + } + defer impl.ciPipelineRepository.RollbackTx(tx) + oldAppWorkflowMapping, err := impl.deleteOldCiPipelineAndWorkflowMappingBeforeSwitch(tx, switchFromPipelineId, switchFromType, userId) + if err != nil { + impl.logger.Errorw("error in deleting old ci-pipeline and getting the appWorkflow mapping of that", "err", err, "userId", userId) + return nil, err + } + + ciConfig.CiPipelines = []*bean.CiPipeline{ciPipelineReq} //request.CiPipeline + res, err := impl.ciCdPipelineOrchestrator.AddPipelineToTemplate(ciConfig, true) + if err != nil { + impl.logger.Errorw("error in adding pipeline to template", "ciConf", ciConfig, "err", err) + return nil, err + } + + //go and update all the app workflow mappings of old ci_pipeline with new ci_pipeline_id. + err = impl.updateLinkedAppWorkflowMappings(tx, oldAppWorkflowMapping, res.AppWorkflowMapping) + if err != nil { + impl.logger.Errorw("error in updating app workflow mappings", "err", err) + return nil, err + } + + //we don't store ci-pipeline-id in pipeline table for external ci's + if switchFromPipelineId > 0 && switchFromType != bean.EXTERNAL { + // ciPipeline id is being set in res object in the addpipelineToTemplate method. + err = impl.pipelineRepository.UpdateOldCiPipelineIdToNewCiPipelineId(tx, switchFromPipelineId, res.CiPipelines[0].Id) + if err != nil { + impl.logger.Errorw("error in updating pipelines ci_pipeline_ids with new ci_pipelineId", "oldCiPipelineId", switchFromPipelineId, "newCiPipelineId", res.CiPipelines[0].Id) + return nil, err + } + } + + err = impl.ciPipelineRepository.CommitTx(tx) + if err != nil { + impl.logger.Errorw("error in committing the transaction", "switchFromPipelineId", switchFromPipelineId, "switchFromType", switchFromType, "userId", userId, "err", err) + return nil, err + } + return res, nil +} + +// add switchType and remove other id +// make constants for error msgs +func (impl *BuildPipelineSwitchServiceImpl) validateCiPipelineSwitch(switchFromCiPipelineId int, switchToType, switchFromType bean.PipelineType) error { + // this will only allow below conversions + // ext -> {ci_job,direct,linked} + // direct -> {ci_job,linked} + // linked -> {ci_job,direct} + // ci_job -> {direct,linked} + + if switchToType == switchFromType { + return errors.New(string(cannotConvertToSameType)) + } + + // refer SwitchToExternalCi + if switchToType == bean.EXTERNAL { + return errors.New(string(cannotConvertToExternalCi)) + } + + // we should not check the below logic for external_ci type as builds are not built in devtron and + // linked pipelines won't be there as per current external-ci-pipeline architecture + if switchFromCiPipelineId > 0 && switchFromType != bean.EXTERNAL { + // old ci_pipeline should not contain any linked ci_pipelines. + linkedCiPipelines, err := impl.ciPipelineRepository.FindLinkedCiCount(switchFromCiPipelineId) + if err != nil { + return nil + } + if linkedCiPipelines > 0 { + return errors.New(string(cannotConvertIfLinkedCiFound)) + } + + // note: ideally we should have found any builds running on old ci_pipeline, if yes block this conversion with proper message. + // but checking only latest wf for now. + ciWorkflow, err := impl.ciWorkflowRepository.FindLastTriggeredWorkflow(switchFromCiPipelineId) + // no build is triggered case + if err == pg.ErrNoRows { + return nil + } + if err != nil { + impl.logger.Errorw("error in finding latest ciwokflow by ciPipelineId", "ciPipelineId", switchFromCiPipelineId) + return err + } + + if ciWorkflow.InProgress() { + return errors.New(string(cannotConvertIfLatestWorkflowIsInNonTerminalState)) + } + } + + return nil +} +func (impl *BuildPipelineSwitchServiceImpl) deleteCiAndItsWorkflowMappings(tx *pg.Tx, switchFromPipelineId int, userId int32) error { + ciPipeline, err := impl.ciPipelineRepository.FindById(switchFromPipelineId) + if err == pg.ErrNoRows { + impl.logger.Errorw("no ci-pipeline found for given switchFromCiPipelineId", "switchFromCiPipelineId", switchFromPipelineId) + return errors.New("requested ci pipeline doesn't exist") + } + if err != nil { + impl.logger.Errorw("error in finding ci-pipeline by id", "ciPipelineId", switchFromPipelineId, "err", err) + return err + } + err = impl.deleteBuildPipeline(tx, ciPipeline, userId) + if err != nil { + impl.logger.Errorw("error in deleting ciPipeline", "ciPipelineId", ciPipeline, "err", err) + } + return err +} + +func (impl *BuildPipelineSwitchServiceImpl) deleteOldCiPipelineAndWorkflowMappingBeforeSwitch(tx *pg.Tx, switchFromPipelineId int, switchFromType bean.PipelineType, userId int32) (*appWorkflow.AppWorkflowMapping, error) { + // 1) delete build pipelines + // 2) delete app workflowMappings + var err error + pipelineId := switchFromPipelineId + pipelineType := "" + if switchFromType == bean.EXTERNAL { + err = impl.deleteExternalCi(tx, switchFromPipelineId, userId) + pipelineType = appWorkflow.WEBHOOK + } else { + err = impl.deleteCiAndItsWorkflowMappings(tx, switchFromPipelineId, userId) + pipelineType = appWorkflow.CIPIPELINE + } + if err != nil { + impl.logger.Errorw("error in deleting ci pipeline before switching ", "switchFromPipelineId", switchFromPipelineId, "switchFromType", switchFromType, "err", err) + return nil, err + } + return impl.deleteAndGetAppWorkflowMappings(tx, pipelineType, pipelineId, userId) +} + +func (impl *BuildPipelineSwitchServiceImpl) deleteAndGetAppWorkflowMappings(tx *pg.Tx, pipelineType string, pipelineId int, userId int32) (*appWorkflow.AppWorkflowMapping, error) { + appWorkflowMappings, err := impl.appWorkflowRepository.FindWFMappingByComponent(pipelineType, pipelineId) + if err != nil { + impl.logger.Errorw("error in getting appWorkflowMappings", "err", err, "pipelineType", pipelineType, "pipelineId", pipelineId) + return appWorkflowMappings, err + } + //deleting app workflow mapping in tx + appWorkflowMappings.UpdatedBy = userId + appWorkflowMappings.UpdatedOn = time.Now() + err = impl.appWorkflowRepository.DeleteAppWorkflowMapping(appWorkflowMappings, tx) + if err != nil { + impl.logger.Errorw("error in deleting workflow mapping", "CiPipelineType", pipelineType, "pipelineId", pipelineId, "err", err) + return appWorkflowMappings, err + } + return appWorkflowMappings, nil +} + +func (impl *BuildPipelineSwitchServiceImpl) deleteBuildPipeline(tx *pg.Tx, ciPipeline *pipelineConfig.CiPipeline, userId int32) error { + err := impl.ciCdPipelineOrchestrator.DeleteCiPipelineAndCiEnvMappings(tx, ciPipeline, userId) + if err != nil { + impl.logger.Errorw("error in deleting ci pipeline and its env mappings", "pipelineId", ciPipeline.Id, "err", err) + return err + } + materials, err := impl.DeleteCiMaterial(tx, ciPipeline) + if err != nil { + return err + } + if !ciPipeline.IsDockerConfigOverridden { + err = impl.ciCdPipelineOrchestrator.SaveHistoryOfBaseTemplate(userId, ciPipeline, materials) + if err != nil { + impl.logger.Errorw("error in saving history of base template", "pipelineId", ciPipeline.Id, "err", err) + return err + } + } else if ciPipeline.ParentCiPipeline == 0 { + err = impl.saveHistoryOfOverriddenTemplate(ciPipeline, userId, materials) + if err != nil { + impl.logger.Errorw("error in saving history for overridden template", "pipelineId", ciPipeline.Id, "err", err) + return err + } + } + return err +} + +func (impl *BuildPipelineSwitchServiceImpl) deleteExternalCi(tx *pg.Tx, externalCiPipelineId int, userId int32) error { + externalCiPipeline, err := impl.ciPipelineRepository.FindExternalCiById(externalCiPipelineId) + externalCiPipeline.Active = false + externalCiPipeline.AuditLog = sql.AuditLog{UpdatedBy: userId, UpdatedOn: time.Now()} + _, err = impl.ciPipelineRepository.UpdateExternalCi(externalCiPipeline, tx) + if err != nil { + impl.logger.Errorw("error in deleting workflow mapping", "externalCiPipelineId", externalCiPipelineId, "err", err) + return err + } + return nil +} + +func (impl *BuildPipelineSwitchServiceImpl) DeleteCiMaterial(tx *pg.Tx, ciPipeline *pipelineConfig.CiPipeline) ([]*pipelineConfig.CiPipelineMaterial, error) { + materialDbObject, err := impl.ciPipelineMaterialRepository.GetByPipelineId(ciPipeline.Id) + var materials []*pipelineConfig.CiPipelineMaterial + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error in getting ci pipeline materials", "externalCiPipelineId", "ciPipelineId", ciPipeline.Id, "err", err) + return materials, err + } + if len(materialDbObject) == 0 { + return materials, nil + } + for _, material := range materialDbObject { + material.Active = false + materials = append(materials, material) + } + err = impl.ciPipelineMaterialRepository.Update(tx, materials...) + if err != nil { + impl.logger.Errorw("error in updating ci pipeline materials, DeleteCiPipeline", "err", err, "pipelineId", ciPipeline.Id) + return materials, err + } + return materials, nil +} + +func (impl *BuildPipelineSwitchServiceImpl) saveHistoryOfOverriddenTemplate(ciPipeline *pipelineConfig.CiPipeline, userId int32, materials []*pipelineConfig.CiPipelineMaterial) error { + ciTemplate, err := impl.ciTemplateOverrideRepository.FindByCiPipelineId(ciPipeline.Id) + if err != nil { + impl.logger.Errorw("error in getting ciTemplate ", "err", err, "ciTemplate", ciTemplate.Id) + return err + } + buildConfig, err := bean3.ConvertDbBuildConfigToBean(ciTemplate.CiBuildConfig) + if err != nil { + impl.logger.Errorw("error in ConvertDbBuildConfigToBean ", "err", err, "buildConfigId", buildConfig.Id) + return err + } + CiTemplateBean := impl.ciCdPipelineOrchestrator.CreateCiTemplateBean(ciPipeline.Id, ciTemplate.DockerRegistry.Id, ciTemplate.DockerRepository, ciTemplate.GitMaterialId, buildConfig, userId) + err = impl.ciPipelineHistoryService.SaveHistory(ciPipeline, materials, &CiTemplateBean, repository4.TRIGGER_DELETE) + if err != nil { + impl.logger.Errorw("error in saving delete history for ci pipeline material and ci template overridden", "err", err) + } + return nil +} + +func (impl *BuildPipelineSwitchServiceImpl) updateLinkedAppWorkflowMappings(tx *pg.Tx, oldAppWorkflowMapping *appWorkflow.AppWorkflowMapping, newAppWorkflowMapping *appWorkflow.AppWorkflowMapping) error { + return impl.appWorkflowRepository.UpdateParentComponentDetails(tx, oldAppWorkflowMapping.ComponentId, oldAppWorkflowMapping.Type, newAppWorkflowMapping.ComponentId, newAppWorkflowMapping.Type) +} diff --git a/pkg/pipeline/CiCdPipelineOrchestrator.go b/pkg/pipeline/CiCdPipelineOrchestrator.go index c1a58d5d3b8..70e66568b2a 100644 --- a/pkg/pipeline/CiCdPipelineOrchestrator.go +++ b/pkg/pipeline/CiCdPipelineOrchestrator.go @@ -73,10 +73,13 @@ type CiCdPipelineOrchestrator interface { CreateCDPipelines(pipelineRequest *bean.CDPipelineConfigObject, appId int, userId int32, tx *pg.Tx, appName string) (pipelineId int, err error) UpdateCDPipeline(pipelineRequest *bean.CDPipelineConfigObject, userId int32, tx *pg.Tx) (pipeline *pipelineConfig.Pipeline, err error) DeleteCiPipeline(pipeline *pipelineConfig.CiPipeline, request *bean.CiPatchRequest, tx *pg.Tx) error + DeleteCiPipelineAndCiEnvMappings(tx *pg.Tx, ciPipeline *pipelineConfig.CiPipeline, userId int32) error + SaveHistoryOfBaseTemplate(userId int32, pipeline *pipelineConfig.CiPipeline, materials []*pipelineConfig.CiPipelineMaterial) error DeleteCdPipeline(pipelineId int, userId int32, tx *pg.Tx) error PatchMaterialValue(createRequest *bean.CiPipeline, userId int32, oldPipeline *pipelineConfig.CiPipeline) (*bean.CiPipeline, error) PatchCiMaterialSource(ciPipeline *bean.CiMaterialPatchRequest, userId int32) (*bean.CiMaterialPatchRequest, error) PatchCiMaterialSourceValue(patchRequest *bean.CiMaterialValuePatchRequest, userId int32, value string, token string, checkAppSpecificAccess func(token, action string, appId int) (bool, error)) (*pipelineConfig.CiPipelineMaterial, error) + CreateCiTemplateBean(ciPipelineId int, dockerRegistryId string, dockerRepository string, gitMaterialId int, ciBuildConfig *bean2.CiBuildConfigBean, userId int32) bean2.CiTemplateBean UpdateCiPipelineMaterials(materialsUpdate []*pipelineConfig.CiPipelineMaterial) error PipelineExists(name string) (bool, error) GetCdPipelinesForApp(appId int) (cdPipelines *bean.CdPipelines, err error) @@ -87,6 +90,7 @@ type CiCdPipelineOrchestrator interface { CheckStringMatchRegex(regex string, value string) bool CreateEcrRepo(dockerRepository, AWSRegion, AWSAccessKeyId, AWSSecretAccessKey string) error GetCdPipelinesForEnv(envId int, requestedAppIds []int) (cdPipelines *bean.CdPipelines, err error) + AddPipelineToTemplate(createRequest *bean.CiConfigRequest, isSwitchCiPipelineRequest bool) (resp *bean.CiConfigRequest, err error) } type CiCdPipelineOrchestratorImpl struct { @@ -113,6 +117,7 @@ type CiCdPipelineOrchestratorImpl struct { gitMaterialHistoryService history3.GitMaterialHistoryService ciPipelineHistoryService history3.CiPipelineHistoryService dockerArtifactStoreRepository dockerRegistryRepository.DockerArtifactStoreRepository + CiArtifactRepository repository.CiArtifactRepository configMapService ConfigMapService genericNoteService genericNotes.GenericNoteService customTagService CustomTagService @@ -140,6 +145,7 @@ func NewCiCdPipelineOrchestrator( ciPipelineHistoryService history3.CiPipelineHistoryService, ciTemplateService CiTemplateService, dockerArtifactStoreRepository dockerRegistryRepository.DockerArtifactStoreRepository, + CiArtifactRepository repository.CiArtifactRepository, configMapService ConfigMapService, customTagService CustomTagService, genericNoteService genericNotes.GenericNoteService) *CiCdPipelineOrchestratorImpl { @@ -166,6 +172,7 @@ func NewCiCdPipelineOrchestrator( ciPipelineHistoryService: ciPipelineHistoryService, ciTemplateService: ciTemplateService, dockerArtifactStoreRepository: dockerArtifactStoreRepository, + CiArtifactRepository: CiArtifactRepository, configMapService: configMapService, genericNoteService: genericNoteService, customTagService: customTagService, @@ -640,36 +647,13 @@ func (impl CiCdPipelineOrchestratorImpl) PatchMaterialValue(createRequest *bean. return createRequest, nil } +// todo: extract common logic into separate func and use that here and in switchService func (impl CiCdPipelineOrchestratorImpl) DeleteCiPipeline(pipeline *pipelineConfig.CiPipeline, request *bean.CiPatchRequest, tx *pg.Tx) error { userId := request.UserId - CiEnvMappingObject, err := impl.ciPipelineRepository.FindCiEnvMappingByCiPipelineId(pipeline.Id) - if err != nil && err != pg.ErrNoRows { - impl.logger.Errorw("error in getting CiEnvMappingObject ", "err", err, "ciPipelineId", pipeline.Id) - return err - } - if err == nil && CiEnvMappingObject != nil { - CiEnvMappingObject.AuditLog = sql.AuditLog{UpdatedBy: userId, UpdatedOn: time.Now()} - CiEnvMappingObject.Deleted = true - err = impl.ciPipelineRepository.UpdateCiEnvMapping(CiEnvMappingObject, tx) - if err != nil { - impl.logger.Errorw("error in getting CiEnvMappingObject ", "err", err, "ciPipelineId", CiEnvMappingObject.CiPipelineId) - return err - } - } - - p := &pipelineConfig.CiPipeline{ - Id: pipeline.Id, - Deleted: true, - ScanEnabled: pipeline.ScanEnabled, - IsManual: pipeline.IsManual, - IsDockerConfigOverridden: pipeline.IsDockerConfigOverridden, - AuditLog: sql.AuditLog{UpdatedBy: userId, UpdatedOn: time.Now()}, - } - - err = impl.ciPipelineRepository.Update(p, tx) + err := impl.DeleteCiPipelineAndCiEnvMappings(tx, pipeline, userId) if err != nil { - impl.logger.Errorw("error in updating ci pipeline, DeleteCiPipeline", "err", err, "pipelineId", pipeline.Id) + impl.logger.Errorw("error in deleting ciPipeline and ci-env mappings", "err", err, "pipelineId", pipeline.Id) return err } var materials []*pipelineConfig.CiPipelineMaterial @@ -689,44 +673,22 @@ func (impl CiCdPipelineOrchestratorImpl) DeleteCiPipeline(pipeline *pipelineConf return err } } - - err = impl.ciPipelineMaterialRepository.Update(tx, materials...) - if err != nil { - impl.logger.Errorw("error in updating ci pipeline materials, DeleteCiPipeline", "err", err, "pipelineId", pipeline.Id) - return err + if len(materials) > 0 { + err = impl.ciPipelineMaterialRepository.Update(tx, materials...) + if err != nil { + impl.logger.Errorw("error in updating ci pipeline materials, DeleteCiPipeline", "err", err, "pipelineId", pipeline.Id) + return err + } } + if !request.CiPipeline.IsDockerConfigOverridden || request.CiPipeline.IsExternal { //if pipeline is external or if config is not overridden then ignore override and ciBuildConfig values - CiTemplateBean := bean2.CiTemplateBean{ - CiTemplate: nil, - CiTemplateOverride: &pipelineConfig.CiTemplateOverride{}, - CiBuildConfig: &bean2.CiBuildConfigBean{}, - UserId: userId, - } - err = impl.ciPipelineHistoryService.SaveHistory(p, materials, &CiTemplateBean, repository4.TRIGGER_DELETE) + err = impl.SaveHistoryOfBaseTemplate(userId, pipeline, materials) if err != nil { - impl.logger.Errorw("error in saving delete history for ci pipeline material and ci template overridden", "err", err) + return err } } else { - CiTemplateBean := bean2.CiTemplateBean{ - CiTemplate: nil, - CiTemplateOverride: &pipelineConfig.CiTemplateOverride{ - CiPipelineId: request.CiPipeline.Id, - DockerRegistryId: request.CiPipeline.DockerConfigOverride.DockerRegistry, - DockerRepository: request.CiPipeline.DockerConfigOverride.DockerRepository, - //DockerfilePath: ciPipelineRequest.DockerConfigOverride.DockerBuildConfig.DockerfilePath, - GitMaterialId: request.CiPipeline.DockerConfigOverride.CiBuildConfig.GitMaterialId, - Active: false, - AuditLog: sql.AuditLog{ - CreatedBy: userId, - CreatedOn: time.Now(), - UpdatedBy: userId, - UpdatedOn: time.Now(), - }, - }, - CiBuildConfig: request.CiPipeline.DockerConfigOverride.CiBuildConfig, - UserId: userId, - } - err = impl.ciPipelineHistoryService.SaveHistory(p, materials, &CiTemplateBean, repository4.TRIGGER_DELETE) + CiTemplateBean := impl.CreateCiTemplateBean(request.CiPipeline.Id, request.CiPipeline.DockerConfigOverride.DockerRegistry, request.CiPipeline.DockerConfigOverride.DockerRepository, request.CiPipeline.DockerConfigOverride.CiBuildConfig.GitMaterialId, request.CiPipeline.DockerConfigOverride.CiBuildConfig, userId) + err = impl.ciPipelineHistoryService.SaveHistory(pipeline, materials, &CiTemplateBean, repository4.TRIGGER_DELETE) if err != nil { impl.logger.Errorw("error in saving delete history for ci pipeline material and ci template overridden", "err", err) } @@ -735,6 +697,79 @@ func (impl CiCdPipelineOrchestratorImpl) DeleteCiPipeline(pipeline *pipelineConf return err } +func (impl CiCdPipelineOrchestratorImpl) DeleteCiPipelineAndCiEnvMappings(tx *pg.Tx, ciPipeline *pipelineConfig.CiPipeline, userId int32) error { + err := impl.deleteCiEnvMapping(tx, ciPipeline, userId) + if err != nil { + impl.logger.Errorw("error in deleting ci-env mappings", "ciPipelineId", ciPipeline.Id, "err", err) + return err + } + ciPipeline.Deleted = true + ciPipeline.Active = false + ciPipeline.UpdatedOn = time.Now() + ciPipeline.UpdatedBy = userId + err = impl.ciPipelineRepository.Update(ciPipeline, tx) + if err != nil { + impl.logger.Errorw("error in updating ci pipeline, DeleteCiPipeline", "pipelineId", ciPipeline.Id, "err", err) + return err + } + return err +} + +func (impl CiCdPipelineOrchestratorImpl) CreateCiTemplateBean(ciPipelineId int, dockerRegistryId string, dockerRepository string, gitMaterialId int, ciBuildConfig *bean2.CiBuildConfigBean, userId int32) bean2.CiTemplateBean { + CiTemplateBean := bean2.CiTemplateBean{ + CiTemplate: nil, + CiTemplateOverride: &pipelineConfig.CiTemplateOverride{ + CiPipelineId: ciPipelineId, + DockerRegistryId: dockerRegistryId, + DockerRepository: dockerRepository, + //DockerfilePath: ciPipelineRequest.DockerConfigOverride.DockerBuildConfig.DockerfilePath, + GitMaterialId: gitMaterialId, + Active: false, + AuditLog: sql.AuditLog{ + CreatedBy: userId, + CreatedOn: time.Now(), + UpdatedBy: userId, + UpdatedOn: time.Now(), + }, + }, + CiBuildConfig: ciBuildConfig, + UserId: userId, + } + return CiTemplateBean +} + +func (impl CiCdPipelineOrchestratorImpl) SaveHistoryOfBaseTemplate(userId int32, pipeline *pipelineConfig.CiPipeline, materials []*pipelineConfig.CiPipelineMaterial) error { + CiTemplateBean := bean2.CiTemplateBean{ + CiTemplate: nil, + CiTemplateOverride: &pipelineConfig.CiTemplateOverride{}, + CiBuildConfig: &bean2.CiBuildConfigBean{}, + UserId: userId, + } + err := impl.ciPipelineHistoryService.SaveHistory(pipeline, materials, &CiTemplateBean, repository4.TRIGGER_DELETE) + if err != nil { + impl.logger.Errorw("error in saving delete history for ci pipeline material and ci template overridden", "err", err) + } + return err +} + +func (impl CiCdPipelineOrchestratorImpl) deleteCiEnvMapping(tx *pg.Tx, ciPipeline *pipelineConfig.CiPipeline, userId int32) error { + CiEnvMappingObject, err := impl.ciPipelineRepository.FindCiEnvMappingByCiPipelineId(ciPipeline.Id) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error in getting CiEnvMappingObject ", "err", err, "ciPipelineId", ciPipeline.Id) + return err + } + + if err == nil && CiEnvMappingObject != nil { + CiEnvMappingObject.AuditLog = sql.AuditLog{UpdatedBy: userId, UpdatedOn: time.Now()} + CiEnvMappingObject.Deleted = true + err = impl.ciPipelineRepository.UpdateCiEnvMapping(CiEnvMappingObject, tx) + if err != nil { + impl.logger.Errorw("error in getting CiEnvMappingObject ", "err", err, "ciPipelineId", CiEnvMappingObject.CiPipelineId) + return err + } + } + return nil +} func (impl CiCdPipelineOrchestratorImpl) CreateCiConf(createRequest *bean.CiConfigRequest, templateId int) (*bean.CiConfigRequest, error) { //save pipeline in db start for _, ciPipeline := range createRequest.CiPipelines { @@ -880,6 +915,7 @@ func (impl CiCdPipelineOrchestratorImpl) CreateCiConf(createRequest *bean.CiConf if err != nil && pg.ErrNoRows != err { return createRequest, err } + if appWorkflowModel.Id > 0 { appWorkflowMap := &appWorkflow.AppWorkflowMapping{ AppWorkflowId: appWorkflowModel.Id, @@ -894,11 +930,22 @@ func (impl CiCdPipelineOrchestratorImpl) CreateCiConf(createRequest *bean.CiConf if err != nil { return createRequest, err } + createRequest.AppWorkflowMapping = appWorkflowMap } err = tx.Commit() if err != nil { return nil, err } + // to copy artifacts in certain cases + if createRequest.Artifact != nil { + createRequest.Artifact.PipelineId = ciPipeline.Id + _, err := impl.CiArtifactRepository.SaveAll([]*repository.CiArtifact{createRequest.Artifact}) + if err != nil { + impl.logger.Errorw("error in saving artifacts for CI pipeline", "artifact", createRequest, "err", err) + return nil, err + } + } + ciTemplateBean := &bean2.CiTemplateBean{} if ciPipeline.IsDockerConfigOverridden { //creating template override @@ -1963,3 +2010,47 @@ func (impl CiCdPipelineOrchestratorImpl) CreateEcrRepo(dockerRepository, AWSRegi } return nil } + +func (impl CiCdPipelineOrchestratorImpl) AddPipelineToTemplate(createRequest *bean.CiConfigRequest, isSwitchCiPipelineRequest bool) (resp *bean.CiConfigRequest, err error) { + + if createRequest.AppWorkflowId == 0 { + // create workflow + wf := &appWorkflow.AppWorkflow{ + Name: fmt.Sprintf("wf-%d-%s", createRequest.AppId, util2.Generate(4)), + AppId: createRequest.AppId, + Active: true, + AuditLog: sql.AuditLog{ + CreatedOn: time.Now(), + UpdatedOn: time.Now(), + CreatedBy: createRequest.UserId, + UpdatedBy: createRequest.UserId, + }, + } + savedAppWf, err := impl.appWorkflowRepository.SaveAppWorkflow(wf) + if err != nil { + impl.logger.Errorw("err", err) + return nil, err + } + // workflow creation ends + createRequest.AppWorkflowId = savedAppWf.Id + } + //single ci in same wf validation + workflowMapping, err := impl.appWorkflowRepository.FindWFCIMappingByWorkflowId(createRequest.AppWorkflowId) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error in fetching workflow mapping for ci validation", "err", err) + return nil, err + } + + if !isSwitchCiPipelineRequest && len(workflowMapping) > 0 { + return nil, &util.ApiError{ + InternalMessage: "pipeline already exists", + UserDetailMessage: fmt.Sprintf("pipeline already exists in workflow"), + UserMessage: fmt.Sprintf("pipeline already exists in workflow")} + } + + createRequest, err = impl.CreateCiConf(createRequest, createRequest.Id) + if err != nil { + return nil, err + } + return createRequest, err +} diff --git a/pkg/pipeline/DeploymentPipelineConfigService.go b/pkg/pipeline/DeploymentPipelineConfigService.go index e703e3accf5..24f7c99d82e 100644 --- a/pkg/pipeline/DeploymentPipelineConfigService.go +++ b/pkg/pipeline/DeploymentPipelineConfigService.go @@ -109,7 +109,6 @@ type CdPipelineConfigService interface { 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), ctx context.Context) (*cluster.ResourceGroupingResponse, error) IsGitopsConfigured() (bool, error) RegisterInACD(gitOpsRepoName string, chartGitAttr *util.ChartGitAttribute, userId int32, ctx context.Context) error - CreateExternalCiAndAppWorkflowMapping(appId, appWorkflowId int, userId int32, tx *pg.Tx) (int, error) } type CdPipelineConfigServiceImpl struct { @@ -144,6 +143,8 @@ type CdPipelineConfigServiceImpl struct { customTagService CustomTagService pipelineConfigListenerService PipelineConfigListenerService devtronAppCMCSService DevtronAppCMCSService + ciPipelineConfigService CiPipelineConfigService + buildPipelineSwitchService BuildPipelineSwitchService } func NewCdPipelineConfigServiceImpl( @@ -177,7 +178,9 @@ func NewCdPipelineConfigServiceImpl( application application.ServiceClient, customTagService CustomTagService, pipelineConfigListenerService PipelineConfigListenerService, - devtronAppCMCSService DevtronAppCMCSService) *CdPipelineConfigServiceImpl { + devtronAppCMCSService DevtronAppCMCSService, + ciPipelineConfigService CiPipelineConfigService, + buildPipelineSwitchService BuildPipelineSwitchService) *CdPipelineConfigServiceImpl { return &CdPipelineConfigServiceImpl{ logger: logger, pipelineRepository: pipelineRepository, @@ -210,6 +213,8 @@ func NewCdPipelineConfigServiceImpl( pipelineConfigListenerService: pipelineConfigListenerService, devtronAppCMCSService: devtronAppCMCSService, customTagService: customTagService, + ciPipelineConfigService: ciPipelineConfigService, + buildPipelineSwitchService: buildPipelineSwitchService, } } @@ -353,6 +358,10 @@ func (impl *CdPipelineConfigServiceImpl) CreateCdPipelines(pipelineCreateRequest isGitOpsConfigured, err := impl.IsGitopsConfigured() for _, pipeline := range pipelineCreateRequest.Pipelines { + // skip creation of pipeline if envId is not set + if pipeline.EnvironmentId <= 0 { + continue + } // if no deployment app type sent from user then we'll not validate deploymentConfig, err := impl.devtronAppCMCSService.GetDeploymentConfigMap(pipeline.EnvironmentId) if err != nil { @@ -406,20 +415,22 @@ func (impl *CdPipelineConfigServiceImpl) CreateCdPipelines(pipelineCreateRequest return nil, err } pipeline.Id = id - - //creating pipeline_stage entry here after tx commit due to FK issue - if pipeline.PreDeployStage != nil && len(pipeline.PreDeployStage.Steps) > 0 { - err = impl.pipelineStageService.CreatePipelineStage(pipeline.PreDeployStage, repository5.PIPELINE_STAGE_TYPE_PRE_CD, id, pipelineCreateRequest.UserId) - if err != nil { - impl.logger.Errorw("error in creating pre-cd stage", "err", err, "preCdStage", pipeline.PreDeployStage, "pipelineId", id) - return nil, err + //go for stage creation if pipeline is created above + if pipeline.Id > 0 { + //creating pipeline_stage entry here after tx commit due to FK issue + if pipeline.PreDeployStage != nil && len(pipeline.PreDeployStage.Steps) > 0 { + err = impl.pipelineStageService.CreatePipelineStage(pipeline.PreDeployStage, repository5.PIPELINE_STAGE_TYPE_PRE_CD, id, pipelineCreateRequest.UserId) + if err != nil { + impl.logger.Errorw("error in creating pre-cd stage", "err", err, "preCdStage", pipeline.PreDeployStage, "pipelineId", id) + return nil, err + } } - } - if pipeline.PostDeployStage != nil && len(pipeline.PostDeployStage.Steps) > 0 { - err = impl.pipelineStageService.CreatePipelineStage(pipeline.PostDeployStage, repository5.PIPELINE_STAGE_TYPE_POST_CD, id, pipelineCreateRequest.UserId) - if err != nil { - impl.logger.Errorw("error in creating post-cd stage", "err", err, "postCdStage", pipeline.PostDeployStage, "pipelineId", id) - return nil, err + if pipeline.PostDeployStage != nil && len(pipeline.PostDeployStage.Steps) > 0 { + err = impl.pipelineStageService.CreatePipelineStage(pipeline.PostDeployStage, repository5.PIPELINE_STAGE_TYPE_POST_CD, id, pipelineCreateRequest.UserId) + if err != nil { + impl.logger.Errorw("error in creating post-cd stage", "err", err, "postCdStage", pipeline.PostDeployStage, "pipelineId", id) + return nil, err + } } } } @@ -622,6 +633,7 @@ func (impl *CdPipelineConfigServiceImpl) DeleteCdPipeline(pipeline *pipelineConf if len(clusterBean.ErrorInConnecting) > 0 { deleteResponse.ClusterReachable = false } + //getting children CD pipeline details childNodes, err := impl.appWorkflowRepository.FindWFCDMappingByParentCDPipelineId(pipeline.Id) if err != nil && err != pg.ErrNoRows { @@ -1337,7 +1349,7 @@ func (impl *CdPipelineConfigServiceImpl) IsGitOpsRequiredForCD(pipelineCreateReq haveAtLeastOneGitOps := false for _, pipeline := range pipelineCreateRequest.Pipelines { - if pipeline.DeploymentAppType == util.PIPELINE_DEPLOYMENT_TYPE_ACD { + if pipeline.EnvironmentId > 0 && pipeline.DeploymentAppType == util.PIPELINE_DEPLOYMENT_TYPE_ACD { haveAtLeastOneGitOps = true } } @@ -1630,128 +1642,141 @@ func (impl *CdPipelineConfigServiceImpl) createCdPipeline(ctx context.Context, a } // Rollback tx on error. defer tx.Rollback() - if pipeline.AppWorkflowId == 0 && pipeline.ParentPipelineType == "WEBHOOK" { - wf := &appWorkflow.AppWorkflow{ - Name: fmt.Sprintf("wf-%d-%s", app.Id, util2.Generate(4)), - AppId: app.Id, - Active: true, - AuditLog: sql.AuditLog{CreatedBy: userId, CreatedOn: time.Now(), UpdatedOn: time.Now(), UpdatedBy: userId}, - } - savedAppWf, err := impl.appWorkflowRepository.SaveAppWorkflowWithTx(wf, tx) - if err != nil { - impl.logger.Errorw("error in saving app workflow", "appId", app.Id, "err", err) - return 0, err + if (pipeline.AppWorkflowId == 0 || pipeline.IsSwitchCiPipelineRequest()) && pipeline.ParentPipelineType == "WEBHOOK" { + if pipeline.AppWorkflowId == 0 { + wf := &appWorkflow.AppWorkflow{ + Name: fmt.Sprintf("wf-%d-%s", app.Id, util2.Generate(4)), + AppId: app.Id, + Active: true, + AuditLog: sql.AuditLog{CreatedBy: userId, CreatedOn: time.Now(), UpdatedOn: time.Now(), UpdatedBy: userId}, + } + savedAppWf, err := impl.appWorkflowRepository.SaveAppWorkflowWithTx(wf, tx) + if err != nil { + impl.logger.Errorw("error in saving app workflow", "appId", app.Id, "err", err) + return 0, err + } + pipeline.AppWorkflowId = savedAppWf.Id } - externalCiPipelineId, err := impl.CreateExternalCiAndAppWorkflowMapping(app.Id, savedAppWf.Id, userId, tx) + externalCiPipelineId, appWorkflowMapping, err := impl.ciPipelineConfigService.CreateExternalCiAndAppWorkflowMapping(app.Id, pipeline.AppWorkflowId, userId, tx) if err != nil { impl.logger.Errorw("error in creating new external ci pipeline and new app workflow mapping", "appId", app.Id, "err", err) return 0, err } + if pipeline.IsSwitchCiPipelineRequest() { + err = impl.buildPipelineSwitchService.SwitchToExternalCi(tx, appWorkflowMapping, pipeline.SwitchFromCiPipelineId, userId) + if err != nil { + impl.logger.Errorw("error in switching external ci", "appId", app.Id, "switchFromExternalCiPipelineId", pipeline.SwitchFromCiPipelineId, "userId", userId, "err", err) + return 0, err + } + } pipeline.ParentPipelineId = externalCiPipelineId - pipeline.AppWorkflowId = savedAppWf.Id - } - - chart, err := impl.chartRepository.FindLatestChartForAppByAppId(app.Id) - if err != nil { - return 0, err - } - envOverride, err := impl.propertiesConfigService.CreateIfRequired(chart, pipeline.EnvironmentId, userId, false, models.CHARTSTATUS_NEW, false, false, pipeline.Namespace, chart.IsBasicViewLocked, chart.CurrentViewEditor, tx) - if err != nil { - return 0, err } - // Get pipeline override based on Deployment strategy - //TODO: mark as created in our db - pipelineId, err := impl.ciCdPipelineOrchestrator.CreateCDPipelines(pipeline, app.Id, userId, tx, app.AppName) - if err != nil { - impl.logger.Errorw("error in creating cd pipeline", "appId", app.Id, "pipeline", pipeline) - return 0, err - } - if pipeline.RefPipelineId > 0 { - pipeline.SourceToNewPipelineId[pipeline.RefPipelineId] = pipelineId - } - - //adding pipeline to workflow - _, err = impl.appWorkflowRepository.FindByIdAndAppId(pipeline.AppWorkflowId, app.Id) - if err != nil && err != pg.ErrNoRows { - return 0, err - } - if pipeline.AppWorkflowId > 0 { - var parentPipelineId int - var parentPipelineType string - - if pipeline.ParentPipelineId == 0 { - parentPipelineId = pipeline.CiPipelineId - parentPipelineType = "CI_PIPELINE" - } else { - parentPipelineId = pipeline.ParentPipelineId - parentPipelineType = pipeline.ParentPipelineType - if pipeline.ParentPipelineType != appWorkflow.WEBHOOK && pipeline.RefPipelineId > 0 && len(pipeline.SourceToNewPipelineId) > 0 { - parentPipelineId = pipeline.SourceToNewPipelineId[pipeline.ParentPipelineId] - } + // do not create the pipeline if environment is not set + pipelineId := 0 + if pipeline.EnvironmentId > 0 { + chart, err := impl.chartRepository.FindLatestChartForAppByAppId(app.Id) + if err != nil { + return 0, err } - appWorkflowMap := &appWorkflow.AppWorkflowMapping{ - AppWorkflowId: pipeline.AppWorkflowId, - ParentId: parentPipelineId, - ParentType: parentPipelineType, - ComponentId: pipelineId, - Type: "CD_PIPELINE", - Active: true, - AuditLog: sql.AuditLog{CreatedBy: userId, CreatedOn: time.Now(), UpdatedOn: time.Now(), UpdatedBy: userId}, + envOverride, err := impl.propertiesConfigService.CreateIfRequired(chart, pipeline.EnvironmentId, userId, false, models.CHARTSTATUS_NEW, false, false, pipeline.Namespace, chart.IsBasicViewLocked, chart.CurrentViewEditor, tx) + if err != nil { + return 0, err } - _, err = impl.appWorkflowRepository.SaveAppWorkflowMapping(appWorkflowMap, tx) + + // Get pipeline override based on Deployment strategy + //TODO: mark as created in our db + pipelineId, err = impl.ciCdPipelineOrchestrator.CreateCDPipelines(pipeline, app.Id, userId, tx, app.AppName) if err != nil { + impl.logger.Errorw("error in creating cd pipeline", "appId", app.Id, "pipeline", pipeline) return 0, err } - } - //getting global app metrics for cd pipeline create because env level metrics is not created yet - appLevelAppMetricsEnabled := false - appLevelMetrics, err := impl.appLevelMetricsRepository.FindByAppId(app.Id) - if err != nil && err != pg.ErrNoRows { - impl.logger.Errorw("error in getting app level metrics app level", "error", err) - } else if err == nil { - appLevelAppMetricsEnabled = appLevelMetrics.AppMetrics - } - err = impl.deploymentTemplateHistoryService.CreateDeploymentTemplateHistoryFromEnvOverrideTemplate(envOverride, tx, appLevelAppMetricsEnabled, pipelineId) - if err != nil { - impl.logger.Errorw("error in creating entry for env deployment template history", "err", err, "envOverride", envOverride) - return 0, err - } - //VARIABLE_MAPPING_UPDATE - err = impl.scopedVariableManager.ExtractAndMapVariables(envOverride.EnvOverrideValues, envOverride.Id, repository3.EntityTypeDeploymentTemplateEnvLevel, envOverride.UpdatedBy, tx) - if err != nil { - return 0, err - } - // strategies for pipeline ids, there is only one is default - defaultCount := 0 - for _, item := range pipeline.Strategies { - if item.Default { - defaultCount = defaultCount + 1 - if defaultCount > 1 { - impl.logger.Warnw("already have one strategy is default in this pipeline", "strategy", item.DeploymentTemplate) - item.Default = false + if pipeline.RefPipelineId > 0 { + pipeline.SourceToNewPipelineId[pipeline.RefPipelineId] = pipelineId + } + + //adding pipeline to workflow + _, err = impl.appWorkflowRepository.FindByIdAndAppId(pipeline.AppWorkflowId, app.Id) + if err != nil && err != pg.ErrNoRows { + return 0, err + } + if pipeline.AppWorkflowId > 0 { + var parentPipelineId int + var parentPipelineType string + + if pipeline.ParentPipelineId == 0 { + parentPipelineId = pipeline.CiPipelineId + parentPipelineType = "CI_PIPELINE" + } else { + parentPipelineId = pipeline.ParentPipelineId + parentPipelineType = pipeline.ParentPipelineType + if pipeline.ParentPipelineType != appWorkflow.WEBHOOK && pipeline.RefPipelineId > 0 && len(pipeline.SourceToNewPipelineId) > 0 { + parentPipelineId = pipeline.SourceToNewPipelineId[pipeline.ParentPipelineId] + } + } + appWorkflowMap := &appWorkflow.AppWorkflowMapping{ + AppWorkflowId: pipeline.AppWorkflowId, + ParentId: parentPipelineId, + ParentType: parentPipelineType, + ComponentId: pipelineId, + Type: "CD_PIPELINE", + Active: true, + AuditLog: sql.AuditLog{CreatedBy: userId, CreatedOn: time.Now(), UpdatedOn: time.Now(), UpdatedBy: userId}, + } + _, err = impl.appWorkflowRepository.SaveAppWorkflowMapping(appWorkflowMap, tx) + if err != nil { + return 0, err } } - strategy := &chartConfig.PipelineStrategy{ - PipelineId: pipelineId, - Strategy: item.DeploymentTemplate, - Config: string(item.Config), - Default: item.Default, - Deleted: false, - AuditLog: sql.AuditLog{UpdatedBy: userId, CreatedBy: userId, UpdatedOn: time.Now(), CreatedOn: time.Now()}, + //getting global app metrics for cd pipeline create because env level metrics is not created yet + appLevelAppMetricsEnabled := false + appLevelMetrics, err := impl.appLevelMetricsRepository.FindByAppId(app.Id) + if err != nil && err != pg.ErrNoRows { + impl.logger.Errorw("error in getting app level metrics app level", "error", err) + } else if err == nil { + appLevelAppMetricsEnabled = appLevelMetrics.AppMetrics } - err = impl.pipelineConfigRepository.Save(strategy, tx) + err = impl.deploymentTemplateHistoryService.CreateDeploymentTemplateHistoryFromEnvOverrideTemplate(envOverride, tx, appLevelAppMetricsEnabled, pipelineId) if err != nil { - impl.logger.Errorw("error in saving strategy", "strategy", item.DeploymentTemplate) - return pipelineId, fmt.Errorf("pipeline created but failed to add strategy") + impl.logger.Errorw("error in creating entry for env deployment template history", "err", err, "envOverride", envOverride) + return 0, err } - //creating history entry for strategy - _, err = impl.pipelineStrategyHistoryService.CreatePipelineStrategyHistory(strategy, pipeline.TriggerType, tx) + //VARIABLE_MAPPING_UPDATE + err = impl.scopedVariableManager.ExtractAndMapVariables(envOverride.EnvOverrideValues, envOverride.Id, repository3.EntityTypeDeploymentTemplateEnvLevel, envOverride.UpdatedBy, tx) if err != nil { - impl.logger.Errorw("error in creating strategy history entry", "err", err) return 0, err } + // strategies for pipeline ids, there is only one is default + defaultCount := 0 + for _, item := range pipeline.Strategies { + if item.Default { + defaultCount = defaultCount + 1 + if defaultCount > 1 { + impl.logger.Warnw("already have one strategy is default in this pipeline", "strategy", item.DeploymentTemplate) + item.Default = false + } + } + strategy := &chartConfig.PipelineStrategy{ + PipelineId: pipelineId, + Strategy: item.DeploymentTemplate, + Config: string(item.Config), + Default: item.Default, + Deleted: false, + AuditLog: sql.AuditLog{UpdatedBy: userId, CreatedBy: userId, UpdatedOn: time.Now(), CreatedOn: time.Now()}, + } + err = impl.pipelineConfigRepository.Save(strategy, tx) + if err != nil { + impl.logger.Errorw("error in saving strategy", "strategy", item.DeploymentTemplate) + return pipelineId, fmt.Errorf("pipeline created but failed to add strategy") + } + //creating history entry for strategy + _, err = impl.pipelineStrategyHistoryService.CreatePipelineStrategyHistory(strategy, pipeline.TriggerType, tx) + if err != nil { + impl.logger.Errorw("error in creating strategy history entry", "err", err) + return 0, err + } + } } // save custom tag data err = impl.CDPipelineCustomTagDBOperations(pipeline) @@ -2053,30 +2078,3 @@ func (impl *CdPipelineConfigServiceImpl) BulkDeleteCdPipelines(impactedPipelines return respDtos } - -func (impl *CdPipelineConfigServiceImpl) CreateExternalCiAndAppWorkflowMapping(appId, appWorkflowId int, userId int32, tx *pg.Tx) (int, error) { - externalCiPipeline := &pipelineConfig.ExternalCiPipeline{ - AppId: appId, - AccessToken: "", - Active: true, - AuditLog: sql.AuditLog{CreatedBy: userId, CreatedOn: time.Now(), UpdatedOn: time.Now(), UpdatedBy: userId}, - } - externalCiPipeline, err := impl.ciPipelineRepository.SaveExternalCi(externalCiPipeline, tx) - if err != nil { - impl.logger.Errorw("error in saving external ci", "appId", appId, "err", err) - return 0, err - } - appWorkflowMap := &appWorkflow.AppWorkflowMapping{ - AppWorkflowId: appWorkflowId, - ComponentId: externalCiPipeline.Id, - Type: "WEBHOOK", - Active: true, - AuditLog: sql.AuditLog{CreatedBy: userId, CreatedOn: time.Now(), UpdatedOn: time.Now(), UpdatedBy: userId}, - } - appWorkflowMap, err = impl.appWorkflowRepository.SaveAppWorkflowMapping(appWorkflowMap, tx) - if err != nil { - impl.logger.Errorw("error in saving app workflow mapping for external ci", "appId", appId, "appWorkflowId", appWorkflowId, "externalCiPipelineId", externalCiPipeline.Id, "err", err) - return 0, err - } - return externalCiPipeline.Id, nil -} diff --git a/pkg/pipeline/WebhookService.go b/pkg/pipeline/WebhookService.go index b4beb14131a..6168d00e96d 100644 --- a/pkg/pipeline/WebhookService.go +++ b/pkg/pipeline/WebhookService.go @@ -272,7 +272,7 @@ func (impl WebhookServiceImpl) HandleCiSuccessEvent(ciPipelineId int, request *C } } if len(pluginArtifacts) > 0 { - err = impl.ciArtifactRepository.SaveAll(pluginArtifacts) + _, err = impl.ciArtifactRepository.SaveAll(pluginArtifacts) if err != nil { impl.logger.Errorw("error while saving ci artifacts", "err", err) return 0, err @@ -307,7 +307,7 @@ func (impl WebhookServiceImpl) HandleCiSuccessEvent(ciPipelineId int, request *C impl.logger.Debugw("saving ci artifacts", "art", ciArtifactArr) if len(ciArtifactArr) > 0 { - err = impl.ciArtifactRepository.SaveAll(ciArtifactArr) + _, err = impl.ciArtifactRepository.SaveAll(ciArtifactArr) if err != nil { impl.logger.Errorw("error while saving ci artifacts", "err", err) return 0, err diff --git a/pkg/pipeline/WorkflowDagExecutor.go b/pkg/pipeline/WorkflowDagExecutor.go index 3f4fa28c359..f78b6eba14c 100644 --- a/pkg/pipeline/WorkflowDagExecutor.go +++ b/pkg/pipeline/WorkflowDagExecutor.go @@ -988,7 +988,7 @@ func (impl *WorkflowDagExecutorImpl) SavePluginArtifacts(ciArtifact *repository. CDArtifacts = append(CDArtifacts, pluginArtifact) } } - err = impl.ciArtifactRepository.SaveAll(CDArtifacts) + _, err = impl.ciArtifactRepository.SaveAll(CDArtifacts) if err != nil { impl.logger.Errorw("Error in saving artifacts metadata generated by plugin") return CDArtifacts, err @@ -1959,7 +1959,9 @@ func (impl *WorkflowDagExecutorImpl) HandlePostStageSuccessEvent(cdWorkflowId in } //finding ci artifact by ciPipelineID and pipelineId //TODO : confirm values for applyAuth, async & triggeredBy - err = impl.triggerStage(nil, pipeline, ciArtifact, applyAuth, triggeredBy) + + triggerArtifact := ciArtifact + err = impl.triggerStage(nil, pipeline, triggerArtifact, applyAuth, triggeredBy) if err != nil { impl.logger.Errorw("error in triggering cd pipeline after successful post stage", "err", err, "pipelineId", pipeline.Id) return err diff --git a/pkg/pipeline/WorkflowServiceIT_test.go b/pkg/pipeline/WorkflowServiceIT_test.go index 8fd5691f500..c3d189350f5 100644 --- a/pkg/pipeline/WorkflowServiceIT_test.go +++ b/pkg/pipeline/WorkflowServiceIT_test.go @@ -50,7 +50,7 @@ func getWorkflowServiceImpl(t *testing.T) *WorkflowServiceImpl { newChartRepository := chartRepoRepository.NewChartRepository(dbConnection) newCommonServiceImpl := commonService.NewCommonServiceImpl(logger, newChartRepository, newEnvConfigOverrideRepository, nil, nil, nil, nil, nil, nil, nil) mergeUtil := util.MergeUtil{Logger: logger} - appService := app.NewAppService(nil, nil, &mergeUtil, logger, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, newConfigMapRepositoryImpl, nil, nil, nil, nil, nil, newCommonServiceImpl, nil, nil, nil, nil, nil, nil, nil, nil, "", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) + appService := app.NewAppService(nil, nil, &mergeUtil, logger, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, newConfigMapRepositoryImpl, nil, nil, nil, nil, nil, newCommonServiceImpl, nil, nil, nil, nil, nil, nil, nil, nil, "", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) runTimeConfig, _ := client.GetRuntimeConfig() k8sUtil := k8s.NewK8sUtil(logger, runTimeConfig) clusterRepositoryImpl := repository3.NewClusterRepositoryImpl(dbConnection, logger) diff --git a/pkg/pipeline/history/DeployedConfigurationHistoryService.go b/pkg/pipeline/history/DeployedConfigurationHistoryService.go index 3f8c2d2fc8c..89eb5be9bd8 100644 --- a/pkg/pipeline/history/DeployedConfigurationHistoryService.go +++ b/pkg/pipeline/history/DeployedConfigurationHistoryService.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/devtron-labs/devtron/api/bean" + repository2 "github.com/devtron-labs/devtron/internal/sql/repository" "github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig" "github.com/devtron-labs/devtron/pkg/pipeline/history/repository" "github.com/devtron-labs/devtron/pkg/user" @@ -18,6 +19,7 @@ type DeployedConfigurationHistoryService interface { GetDeployedHistoryComponentDetail(ctx context.Context, pipelineId, id int, historyComponent, historyComponentName string, userHasAdminAccess bool) (*HistoryDetailDto, error) GetAllDeployedConfigurationByPipelineIdAndLatestWfrId(ctx context.Context, pipelineId int, userHasAdminAccess bool) (*AllDeploymentConfigurationDetail, error) GetAllDeployedConfigurationByPipelineIdAndWfrId(ctx context.Context, pipelineId, wfrId int, userHasAdminAccess bool) (*AllDeploymentConfigurationDetail, error) + GetLatestDeployedArtifactByPipelineId(pipelineId int) (*repository2.CiArtifact, error) } type DeployedConfigurationHistoryServiceImpl struct { @@ -43,6 +45,15 @@ func NewDeployedConfigurationHistoryServiceImpl(logger *zap.SugaredLogger, } } +func (impl *DeployedConfigurationHistoryServiceImpl) GetLatestDeployedArtifactByPipelineId(pipelineId int) (*repository2.CiArtifact, error) { + wfr, err := impl.cdWorkflowRepository.FindLastStatusByPipelineIdAndRunnerType(pipelineId, bean.CD_WORKFLOW_TYPE_DEPLOY) + if err != nil { + impl.logger.Infow("error in getting latest deploy stage wfr by pipelineId", "err", err, "pipelineId", pipelineId) + return nil, err + } + return wfr.CdWorkflow.CiArtifact, nil +} + func (impl *DeployedConfigurationHistoryServiceImpl) GetDeployedConfigurationByWfrId(pipelineId, wfrId int) ([]*DeploymentConfigurationDto, error) { var deployedConfigurations []*DeploymentConfigurationDto //checking if deployment template configuration for this pipelineId and wfrId exists or not diff --git a/pkg/variables/ScopedVariableService.go b/pkg/variables/ScopedVariableService.go index 161d7d2c691..3b26ebc9e2b 100644 --- a/pkg/variables/ScopedVariableService.go +++ b/pkg/variables/ScopedVariableService.go @@ -522,6 +522,9 @@ func (impl *ScopedVariableServiceImpl) deduceVariables(scopedVariableDataList [] } for _, data := range scopedVariableDataList { + for _, variable := range systemVariables { + varNameToData[variable.VariableName] = variable + } value := data.VariableValue.Value if utils.IsStringType(value) { resolvedValue, err := resolveExpressionWithVariableValues(value.(string), varNameToData) diff --git a/scripts/sql/194_parent_ci_remove_constraint.down.sql b/scripts/sql/194_parent_ci_remove_constraint.down.sql new file mode 100644 index 00000000000..51b610460b3 --- /dev/null +++ b/scripts/sql/194_parent_ci_remove_constraint.down.sql @@ -0,0 +1,4 @@ + + +ALTER TABLE public.ci_pipeline + ADD CONSTRAINT ci_pipeline_parent_ci_pipeline_fkey FOREIGN KEY (parent_ci_pipeline) REFERENCES public.ci_pipeline(id); diff --git a/scripts/sql/194_parent_ci_remove_constraint.up.sql b/scripts/sql/194_parent_ci_remove_constraint.up.sql new file mode 100644 index 00000000000..75913d5e800 --- /dev/null +++ b/scripts/sql/194_parent_ci_remove_constraint.up.sql @@ -0,0 +1 @@ +ALTER TABLE public.ci_pipeline DROP CONSTRAINT ci_pipeline_parent_ci_pipeline_fkey; \ No newline at end of file diff --git a/wire_gen.go b/wire_gen.go index 5a015d2f4da..407aa5ae831 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -480,7 +480,7 @@ func InitializeApp() (*App, error) { ciBuildConfigServiceImpl := pipeline.NewCiBuildConfigServiceImpl(sugaredLogger, ciBuildConfigRepositoryImpl) ciTemplateServiceImpl := pipeline.NewCiTemplateServiceImpl(sugaredLogger, ciBuildConfigServiceImpl, ciTemplateRepositoryImpl, ciTemplateOverrideRepositoryImpl) configMapServiceImpl := pipeline.NewConfigMapServiceImpl(chartRepositoryImpl, sugaredLogger, chartRepoRepositoryImpl, utilMergeUtil, pipelineConfigRepositoryImpl, configMapRepositoryImpl, envConfigOverrideRepositoryImpl, commonServiceImpl, appRepositoryImpl, configMapHistoryServiceImpl, environmentRepositoryImpl, scopedVariableCMCSManagerImpl) - ciCdPipelineOrchestratorImpl := pipeline.NewCiCdPipelineOrchestrator(appRepositoryImpl, sugaredLogger, materialRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, ciPipelineMaterialRepositoryImpl, clientImpl, ciCdConfig, appWorkflowRepositoryImpl, environmentRepositoryImpl, attributesServiceImpl, appListingRepositoryImpl, appCrudOperationServiceImpl, userAuthServiceImpl, prePostCdScriptHistoryServiceImpl, prePostCiScriptHistoryServiceImpl, pipelineStageServiceImpl, ciTemplateOverrideRepositoryImpl, gitMaterialHistoryServiceImpl, ciPipelineHistoryServiceImpl, ciTemplateServiceImpl, dockerArtifactStoreRepositoryImpl, configMapServiceImpl, customTagServiceImpl, genericNoteServiceImpl) + ciCdPipelineOrchestratorImpl := pipeline.NewCiCdPipelineOrchestrator(appRepositoryImpl, sugaredLogger, materialRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, ciPipelineMaterialRepositoryImpl, clientImpl, ciCdConfig, appWorkflowRepositoryImpl, environmentRepositoryImpl, attributesServiceImpl, appListingRepositoryImpl, appCrudOperationServiceImpl, userAuthServiceImpl, prePostCdScriptHistoryServiceImpl, prePostCiScriptHistoryServiceImpl, pipelineStageServiceImpl, ciTemplateOverrideRepositoryImpl, gitMaterialHistoryServiceImpl, ciPipelineHistoryServiceImpl, ciTemplateServiceImpl, dockerArtifactStoreRepositoryImpl, ciArtifactRepositoryImpl, configMapServiceImpl, customTagServiceImpl, genericNoteServiceImpl) ecrConfig, err := pipeline.GetEcrConfig() if err != nil { return nil, err @@ -490,7 +490,8 @@ func InitializeApp() (*App, error) { resourceGroupRepositoryImpl := resourceGroup.NewResourceGroupRepositoryImpl(db) resourceGroupMappingRepositoryImpl := resourceGroup.NewResourceGroupMappingRepositoryImpl(db) resourceGroupServiceImpl := resourceGroup2.NewResourceGroupServiceImpl(sugaredLogger, resourceGroupRepositoryImpl, resourceGroupMappingRepositoryImpl, enforcerUtilImpl, devtronResourceSearchableKeyServiceImpl, appStatusRepositoryImpl) - ciPipelineConfigServiceImpl := pipeline.NewCiPipelineConfigServiceImpl(sugaredLogger, ciCdPipelineOrchestratorImpl, dockerArtifactStoreRepositoryImpl, materialRepositoryImpl, appRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, ecrConfig, appWorkflowRepositoryImpl, ciCdConfig, attributesServiceImpl, pipelineStageServiceImpl, ciPipelineMaterialRepositoryImpl, ciTemplateServiceImpl, ciTemplateOverrideRepositoryImpl, ciTemplateHistoryServiceImpl, enforcerUtilImpl, ciWorkflowRepositoryImpl, resourceGroupServiceImpl, customTagServiceImpl) + buildPipelineSwitchServiceImpl := pipeline.NewBuildPipelineSwitchServiceImpl(sugaredLogger, ciPipelineRepositoryImpl, ciCdPipelineOrchestratorImpl, pipelineRepositoryImpl, ciWorkflowRepositoryImpl, appWorkflowRepositoryImpl, ciPipelineHistoryServiceImpl, ciTemplateOverrideRepositoryImpl, ciPipelineMaterialRepositoryImpl) + ciPipelineConfigServiceImpl := pipeline.NewCiPipelineConfigServiceImpl(sugaredLogger, ciCdPipelineOrchestratorImpl, dockerArtifactStoreRepositoryImpl, materialRepositoryImpl, appRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, ecrConfig, appWorkflowRepositoryImpl, ciCdConfig, attributesServiceImpl, pipelineStageServiceImpl, ciPipelineMaterialRepositoryImpl, ciTemplateServiceImpl, ciTemplateOverrideRepositoryImpl, ciTemplateHistoryServiceImpl, enforcerUtilImpl, ciWorkflowRepositoryImpl, resourceGroupServiceImpl, customTagServiceImpl, ciPipelineHistoryServiceImpl, cdWorkflowRepositoryImpl, buildPipelineSwitchServiceImpl) ciMaterialConfigServiceImpl := pipeline.NewCiMaterialConfigServiceImpl(sugaredLogger, materialRepositoryImpl, ciTemplateServiceImpl, ciCdPipelineOrchestratorImpl, ciPipelineRepositoryImpl, gitMaterialHistoryServiceImpl, pipelineRepositoryImpl, ciPipelineMaterialRepositoryImpl) imageTaggingRepositoryImpl := repository13.NewImageTaggingRepositoryImpl(db) imageTaggingServiceImpl := pipeline.NewImageTaggingServiceImpl(imageTaggingRepositoryImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl, environmentRepositoryImpl, sugaredLogger) @@ -501,7 +502,7 @@ func InitializeApp() (*App, error) { return nil, err } devtronAppCMCSServiceImpl := pipeline.NewDevtronAppCMCSServiceImpl(sugaredLogger, appServiceImpl, attributesRepositoryImpl) - cdPipelineConfigServiceImpl := pipeline.NewCdPipelineConfigServiceImpl(sugaredLogger, pipelineRepositoryImpl, environmentRepositoryImpl, pipelineConfigRepositoryImpl, appWorkflowRepositoryImpl, pipelineStageServiceImpl, appRepositoryImpl, appServiceImpl, deploymentGroupRepositoryImpl, ciCdPipelineOrchestratorImpl, appStatusRepositoryImpl, ciPipelineRepositoryImpl, prePostCdScriptHistoryServiceImpl, clusterRepositoryImpl, helmAppServiceImpl, enforcerUtilImpl, gitOpsConfigRepositoryImpl, pipelineStrategyHistoryServiceImpl, chartRepositoryImpl, resourceGroupServiceImpl, chartDeploymentServiceImpl, chartTemplateServiceImpl, propertiesConfigServiceImpl, appLevelMetricsRepositoryImpl, deploymentTemplateHistoryServiceImpl, scopedVariableManagerImpl, pipelineDeploymentServiceTypeConfig, applicationServiceClientImpl, customTagServiceImpl, pipelineConfigListenerServiceImpl, devtronAppCMCSServiceImpl) + cdPipelineConfigServiceImpl := pipeline.NewCdPipelineConfigServiceImpl(sugaredLogger, pipelineRepositoryImpl, environmentRepositoryImpl, pipelineConfigRepositoryImpl, appWorkflowRepositoryImpl, pipelineStageServiceImpl, appRepositoryImpl, appServiceImpl, deploymentGroupRepositoryImpl, ciCdPipelineOrchestratorImpl, appStatusRepositoryImpl, ciPipelineRepositoryImpl, prePostCdScriptHistoryServiceImpl, clusterRepositoryImpl, helmAppServiceImpl, enforcerUtilImpl, gitOpsConfigRepositoryImpl, pipelineStrategyHistoryServiceImpl, chartRepositoryImpl, resourceGroupServiceImpl, chartDeploymentServiceImpl, chartTemplateServiceImpl, propertiesConfigServiceImpl, appLevelMetricsRepositoryImpl, deploymentTemplateHistoryServiceImpl, scopedVariableManagerImpl, pipelineDeploymentServiceTypeConfig, applicationServiceClientImpl, customTagServiceImpl, pipelineConfigListenerServiceImpl, devtronAppCMCSServiceImpl, ciPipelineConfigServiceImpl, buildPipelineSwitchServiceImpl) appArtifactManagerImpl := pipeline.NewAppArtifactManagerImpl(sugaredLogger, cdWorkflowRepositoryImpl, userServiceImpl, imageTaggingServiceImpl, ciArtifactRepositoryImpl, ciWorkflowRepositoryImpl, pipelineStageServiceImpl, cdPipelineConfigServiceImpl, dockerArtifactStoreRepositoryImpl, ciPipelineRepositoryImpl, ciTemplateServiceImpl) globalStrategyMetadataChartRefMappingRepositoryImpl := chartRepoRepository.NewGlobalStrategyMetadataChartRefMappingRepositoryImpl(db, sugaredLogger) devtronAppStrategyServiceImpl := pipeline.NewDevtronAppStrategyServiceImpl(sugaredLogger, chartRepositoryImpl, globalStrategyMetadataChartRefMappingRepositoryImpl, ciCdPipelineOrchestratorImpl, cdPipelineConfigServiceImpl) @@ -523,7 +524,7 @@ func InitializeApp() (*App, error) { deploymentEventHandlerImpl := app2.NewDeploymentEventHandlerImpl(sugaredLogger, appListingServiceImpl, eventRESTClientImpl, eventSimpleFactoryImpl) cdHandlerImpl := pipeline.NewCdHandlerImpl(sugaredLogger, userServiceImpl, cdWorkflowRepositoryImpl, ciLogServiceImpl, ciArtifactRepositoryImpl, ciPipelineMaterialRepositoryImpl, pipelineRepositoryImpl, environmentRepositoryImpl, ciWorkflowRepositoryImpl, helmAppServiceImpl, pipelineOverrideRepositoryImpl, workflowDagExecutorImpl, appListingServiceImpl, appListingRepositoryImpl, pipelineStatusTimelineRepositoryImpl, applicationServiceClientImpl, argoUserServiceImpl, deploymentEventHandlerImpl, eventRESTClientImpl, pipelineStatusTimelineResourcesServiceImpl, pipelineStatusSyncDetailServiceImpl, pipelineStatusTimelineServiceImpl, appServiceImpl, appStatusServiceImpl, enforcerUtilImpl, installedAppRepositoryImpl, installedAppVersionHistoryRepositoryImpl, appRepositoryImpl, resourceGroupServiceImpl, imageTaggingServiceImpl, k8sUtil, workflowServiceImpl, clusterServiceImplExtended, blobStorageConfigServiceImpl, customTagServiceImpl) appWorkflowServiceImpl := appWorkflow2.NewAppWorkflowServiceImpl(sugaredLogger, appWorkflowRepositoryImpl, ciCdPipelineOrchestratorImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl, enforcerUtilImpl, resourceGroupServiceImpl) - appCloneServiceImpl := appClone.NewAppCloneServiceImpl(sugaredLogger, pipelineBuilderImpl, materialRepositoryImpl, chartServiceImpl, configMapServiceImpl, appWorkflowServiceImpl, appListingServiceImpl, propertiesConfigServiceImpl, ciTemplateOverrideRepositoryImpl, pipelineStageServiceImpl, ciTemplateServiceImpl, appRepositoryImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl, appWorkflowRepositoryImpl) + appCloneServiceImpl := appClone.NewAppCloneServiceImpl(sugaredLogger, pipelineBuilderImpl, materialRepositoryImpl, chartServiceImpl, configMapServiceImpl, appWorkflowServiceImpl, appListingServiceImpl, propertiesConfigServiceImpl, ciTemplateOverrideRepositoryImpl, pipelineStageServiceImpl, ciTemplateServiceImpl, appRepositoryImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl, appWorkflowRepositoryImpl, ciPipelineConfigServiceImpl) deploymentTemplateRepositoryImpl := repository.NewDeploymentTemplateRepositoryImpl(db, sugaredLogger) deploymentTemplateServiceImpl := generateManifest.NewDeploymentTemplateServiceImpl(sugaredLogger, chartServiceImpl, appListingServiceImpl, appListingRepositoryImpl, deploymentTemplateRepositoryImpl, helmAppServiceImpl, chartRepositoryImpl, chartTemplateServiceImpl, helmAppClientImpl, k8sUtil, propertiesConfigServiceImpl, deploymentTemplateHistoryServiceImpl, environmentRepositoryImpl, appRepositoryImpl, scopedVariableManagerImpl) imageScanObjectMetaRepositoryImpl := security.NewImageScanObjectMetaRepositoryImpl(db, sugaredLogger)