Repository: devtron-labs/devtron
Pull request #2655 (2022-11-17 11:45:01)

feat: Audit logs for docker and git repositories
<!--
Type of change: Title of the PR should clearly mention which type of PR is this, you can select any of the below mentioned types:

- docs - The PR contains Documentation ONLY changes. 
- feat - The PR contains new feature/enhancements.
- fix - The PR contains a bug fix.
- chore - Development changes related to the build system (involving scripts, configurations or tools) and package dependencies.
- test - Development changes related to tests.
- perf - Changes related to performance improvements.

Example Title: 
docs: Webhook CI documentation changes
-->

# Description
<!--
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
-->
In order to save audit logs , new tables for history will be created. 
For saving history of git repository, Git Materials history table will be created.
For saving history of docker repository, Ci Template history table will be created.
For saving history of ci pipeline material and ci template override, ci pipeline history table will be created.



## How Has This Been Tested?

For testing Audit logs of git repositories:-

1. Add a git repository, then verify a row is created in git_material_history for creation.
2. Go to same git repo. and change the clone directory to diff. directory, update the git repo and verify creation of another record in the git_material_history.
3. After that, verify Pull Submodules Recursively updating creates another record in git_material_history or not. 
4. Change the same git material to diff. git repo name and check another row inserted in git_material_history or not
5. Delete that git repository, and check if this action introcuces a row in git_material_history or not."
6. Create multiple git repos. and do above actions to check if it works with multi-git

For testing Audit logs of ci template repository:-

1. Add a container registory, and see if it is recorded in the ci_template_history table.
2. Edit and change the container repository to something else and check if that is recorded in the ci_template_history table."
3. Try out with different options(creating, editing,deleting) for selecting dockerfile and check if that is recorded in the ci_template_history table.
4. Play around with Advance options, add, edit them and check if that is recorded in the ci_template_history table."

For testing audit logs of ci pipeline :-

1. Edit Build Pipeline fields and check if the changes are recorded in ci_pipeline_history table.
2. Check if changing the Manually and Automatically field makes a record in  ci_pipeline_history table.
3. Change scan for vulnerabilities option multiple times to see if changes are recorded in the ci_pipeline_history table.
4. Play with override option to see if clicks are recorded in the ci_pipeline_history table.

## Checklist:

* [x] The title of the PR states what changed and the related issues number (used for the release note).
* [ ] Does this PR requires documentation updates?
* [ ] I've updated documentation as required by this PR.
* [x] I have performed a self-review of my own code.
* [ ] I have commented my code, particularly in hard-to-understand areas.
* [ ] I have tested it for all user roles.
* [ ] I have added all the required unit/api test cases.

## Does this PR introduce a user-facing change?
<!--
If NO, leave the release-note block blank.
If YES, a release note is required:
Enter your extended release note in the block below. If the PR requires additional manual action from users switching to the new version, include the string "action-required".

-->
```release-note

```




Patch:
diff --git a/Wire.go b/Wire.go
index cc56998f8..5c580c30b 100644
--- a/Wire.go
+++ b/Wire.go
@@ -686,6 +686,20 @@ func InitializeApp() (*App, error) {
 		wire.Bind(new(repository3.PrePostCdScriptHistoryRepository), new(*repository3.PrePostCdScriptHistoryRepositoryImpl)),
 		repository3.NewPipelineStrategyHistoryRepositoryImpl,
 		wire.Bind(new(repository3.PipelineStrategyHistoryRepository), new(*repository3.PipelineStrategyHistoryRepositoryImpl)),
+		repository3.NewGitMaterialHistoryRepositoyImpl,
+		wire.Bind(new(repository3.GitMaterialHistoryRepository), new(*repository3.GitMaterialHistoryRepositoryImpl)),
+
+		history3.NewCiTemplateHistoryServiceImpl,
+		wire.Bind(new(history3.CiTemplateHistoryService), new(*history3.CiTemplateHistoryServiceImpl)),
+
+		repository3.NewCiTemplateHistoryRepositoryImpl,
+		wire.Bind(new(repository3.CiTemplateHistoryRepository), new(*repository3.CiTemplateHistoryRepositoryImpl)),
+
+		history3.NewCiPipelineHistoryServiceImpl,
+		wire.Bind(new(history3.CiPipelineHistoryService), new(*history3.CiPipelineHistoryServiceImpl)),
+
+		repository3.NewCiPipelineHistoryRepositoryImpl,
+		wire.Bind(new(repository3.CiPipelineHistoryRepository), new(*repository3.CiPipelineHistoryRepositoryImpl)),
 
 		history3.NewPrePostCdScriptHistoryServiceImpl,
 		wire.Bind(new(history3.PrePostCdScriptHistoryService), new(*history3.PrePostCdScriptHistoryServiceImpl)),
@@ -697,6 +711,8 @@ func InitializeApp() (*App, error) {
 		wire.Bind(new(history3.ConfigMapHistoryService), new(*history3.ConfigMapHistoryServiceImpl)),
 		history3.NewPipelineStrategyHistoryServiceImpl,
 		wire.Bind(new(history3.PipelineStrategyHistoryService), new(*history3.PipelineStrategyHistoryServiceImpl)),
+		history3.NewGitMaterialHistoryServiceImpl,
+		wire.Bind(new(history3.GitMaterialHistoryService), new(*history3.GitMaterialHistoryServiceImpl)),
 
 		history3.NewDeployedConfigurationHistoryServiceImpl,
 		wire.Bind(new(history3.DeployedConfigurationHistoryService), new(*history3.DeployedConfigurationHistoryServiceImpl)),
diff --git a/pkg/bean/app.go b/pkg/bean/app.go
index f2f524464..f0fa76cae 100644
--- a/pkg/bean/app.go
+++ b/pkg/bean/app.go
@@ -267,6 +267,10 @@ type CiConfigRequest struct {
 	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"`
 }
 
 type TestExecutorImageProperties struct {
diff --git a/pkg/pipeline/DbPipelineOrchestrator.go b/pkg/pipeline/DbPipelineOrchestrator.go
index 2b5c2f831..e053f387d 100644
--- a/pkg/pipeline/DbPipelineOrchestrator.go
+++ b/pkg/pipeline/DbPipelineOrchestrator.go
@@ -61,9 +61,9 @@ type DbPipelineOrchestrator interface {
 	CreateCiConf(createRequest *bean.CiConfigRequest, templateId int) (*bean.CiConfigRequest, error)
 	CreateCDPipelines(pipelineRequest *bean.CDPipelineConfigObject, appId int, userId int32, tx *pg.Tx) (pipelineId int, err error)
 	UpdateCDPipeline(pipelineRequest *bean.CDPipelineConfigObject, userId int32, tx *pg.Tx) (err error)
-	DeleteCiPipeline(pipeline *pipelineConfig.CiPipeline, userId int32, tx *pg.Tx) error
+	DeleteCiPipeline(pipeline *pipelineConfig.CiPipeline, request *bean.CiPatchRequest, tx *pg.Tx) error
 	DeleteCdPipeline(pipelineId int, tx *pg.Tx) error
-	PatchMaterialValue(createRequest *bean.CiPipeline, userId int32) (*bean.CiPipeline, error)
+	PatchMaterialValue(createRequest *bean.CiPipeline, userId int32, oldPipeline *pipelineConfig.CiPipeline) (*bean.CiPipeline, error)
 	PipelineExists(name string) (bool, error)
 	GetCdPipelinesForApp(appId int) (cdPipelines *bean.CdPipelines, err error)
 	GetCdPipelinesForAppAndEnv(appId int, envId int) (cdPipelines *bean.CdPipelines, err error)
@@ -92,7 +92,10 @@ type DbPipelineOrchestratorImpl struct {
 	prePostCiScriptHistoryService history3.PrePostCiScriptHistoryService
 	pipelineStageService          PipelineStageService
 	//ciTemplateOverrideRepository  pipelineConfig.CiTemplateOverrideRepository
-	ciTemplateService CiTemplateService
+	ciTemplateService            CiTemplateService
+	ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository
+	gitMaterialHistoryService    history3.GitMaterialHistoryService
+	ciPipelineHistoryService     history3.CiPipelineHistoryService
 }
 
 func NewDbPipelineOrchestrator(
@@ -112,7 +115,9 @@ func NewDbPipelineOrchestrator(
 	prePostCdScriptHistoryService history3.PrePostCdScriptHistoryService,
 	prePostCiScriptHistoryService history3.PrePostCiScriptHistoryService,
 	pipelineStageService PipelineStageService,
-	ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository, ciTemplateService CiTemplateService) *DbPipelineOrchestratorImpl {
+	ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository,
+	gitMaterialHistoryService history3.GitMaterialHistoryService,
+	ciPipelineHistoryService history3.CiPipelineHistoryService, ciTemplateService CiTemplateService) *DbPipelineOrchestratorImpl {
 	return &DbPipelineOrchestratorImpl{
 		appRepository:                 pipelineGroupRepository,
 		logger:                        logger,
@@ -131,20 +136,23 @@ func NewDbPipelineOrchestrator(
 		prePostCdScriptHistoryService: prePostCdScriptHistoryService,
 		prePostCiScriptHistoryService: prePostCiScriptHistoryService,
 		pipelineStageService:          pipelineStageService,
-		//ciTemplateOverrideRepository:  ciTemplateOverrideRepository,
-		ciTemplateService: ciTemplateService,
+		ciTemplateOverrideRepository:  ciTemplateOverrideRepository,
+		gitMaterialHistoryService:     gitMaterialHistoryService,
+		ciPipelineHistoryService:      ciPipelineHistoryService,
+		ciTemplateService:             ciTemplateService,
 	}
 }
 
 const BEFORE_DOCKER_BUILD string = "BEFORE_DOCKER_BUILD"
 const AFTER_DOCKER_BUILD string = "AFTER_DOCKER_BUILD"
 
-func (impl DbPipelineOrchestratorImpl) PatchMaterialValue(createRequest *bean.CiPipeline, userId int32) (*bean.CiPipeline, error) {
+func (impl DbPipelineOrchestratorImpl) PatchMaterialValue(createRequest *bean.CiPipeline, userId int32, oldPipeline *pipelineConfig.CiPipeline) (*bean.CiPipeline, error) {
 	argByte, err := json.Marshal(createRequest.DockerArgs)
 	if err != nil {
 		impl.logger.Error(err)
 		return nil, err
 	}
+
 	dbConnection := impl.pipelineRepository.GetConnection()
 	tx, err := dbConnection.Begin()
 	if err != nil {
@@ -166,6 +174,15 @@ func (impl DbPipelineOrchestratorImpl) PatchMaterialValue(createRequest *bean.Ci
 		IsDockerConfigOverridden: createRequest.IsDockerConfigOverridden,
 		AuditLog:                 sql.AuditLog{UpdatedBy: userId, UpdatedOn: time.Now()},
 	}
+
+	createOnTimeMap := make(map[int]time.Time)
+	createByMap := make(map[int]int32)
+
+	for _, oldMaterial := range oldPipeline.CiPipelineMaterials {
+		createOnTimeMap[oldMaterial.GitMaterialId] = oldMaterial.CreatedOn
+		createByMap[oldMaterial.GitMaterialId] = oldMaterial.CreatedBy
+	}
+
 	err = impl.ciPipelineRepository.Update(ciPipelineObject, tx)
 	if err != nil {
 		return nil, err
@@ -217,6 +234,8 @@ func (impl DbPipelineOrchestratorImpl) PatchMaterialValue(createRequest *bean.Ci
 			materialsAdd = append(materialsAdd, pipelineMaterial)
 		} else {
 			materialsUpdate = append(materialsUpdate, pipelineMaterial)
+			pipelineMaterial.CreatedOn = createOnTimeMap[material.GitMaterialId]
+			pipelineMaterial.CreatedBy = createByMap[material.GitMaterialId]
 		}
 	}
 	regexMaterial, err := impl.ciPipelineMaterialRepository.GetRegexByPipelineId(createRequest.Id)
@@ -313,6 +332,7 @@ func (impl DbPipelineOrchestratorImpl) PatchMaterialValue(createRequest *bean.Ci
 				UpdatedBy: userId,
 			},
 		}
+
 		savedTemplateOverride := savedTemplateOverrideBean.CiTemplateOverride
 		if savedTemplateOverride != nil && savedTemplateOverride.Id > 0 {
 			ciBuildConfigBean.Id = savedTemplateOverride.CiBuildConfigId
@@ -328,6 +348,13 @@ func (impl DbPipelineOrchestratorImpl) PatchMaterialValue(createRequest *bean.Ci
 			if err != nil {
 				return nil, err
 			}
+
+			err = impl.ciPipelineHistoryService.SaveHistory(ciPipelineObject, materials, ciTemplateBean, repository4.TRIGGER_UPDATE)
+
+			if err != nil {
+				impl.logger.Errorw("error in saving history of ci pipeline material")
+			}
+
 		} else {
 			ciTemplateBean := &bean2.CiTemplateBean{
 				CiTemplateOverride: templateOverrideReq,
@@ -338,8 +365,28 @@ func (impl DbPipelineOrchestratorImpl) PatchMaterialValue(createRequest *bean.Ci
 			if err != nil {
 				return nil, err
 			}
+
+			err = impl.ciPipelineHistoryService.SaveHistory(ciPipelineObject, materials, ciTemplateBean, repository4.TRIGGER_UPDATE)
+
+			if err != nil {
+				impl.logger.Errorw("error in saving history of ci pipeline material")
+			}
+
+		}
+	} else {
+
+		ciTemplateBean := &bean2.CiTemplateBean{
+			CiTemplateOverride: &pipelineConfig.CiTemplateOverride{},
+			CiBuildConfig:      nil,
+			UserId:             userId,
+		}
+		err = impl.ciPipelineHistoryService.SaveHistory(ciPipelineObject, materials, ciTemplateBean, repository4.TRIGGER_UPDATE)
+
+		if err != nil {
+			impl.logger.Errorw("error in saving history of ci pipeline material")
 		}
 	}
+
 	if len(childrenCiPipelineIds) > 0 {
 
 		ciPipelineMaterials, err := impl.ciPipelineMaterialRepository.FindByCiPipelineIdsIn(childrenCiPipelineIds)
@@ -380,11 +427,17 @@ func (impl DbPipelineOrchestratorImpl) PatchMaterialValue(createRequest *bean.Ci
 	return createRequest, nil
 }
 
-func (impl DbPipelineOrchestratorImpl) DeleteCiPipeline(pipeline *pipelineConfig.CiPipeline, userId int32, tx *pg.Tx) error {
+func (impl DbPipelineOrchestratorImpl) DeleteCiPipeline(pipeline *pipelineConfig.CiPipeline, request *bean.CiPatchRequest, tx *pg.Tx) error {
+
+	userId := request.UserId
+
 	p := &pipelineConfig.CiPipeline{
-		Id:       pipeline.Id,
-		Deleted:  true,
-		AuditLog: sql.AuditLog{UpdatedBy: userId, UpdatedOn: time.Now()},
+		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)
 	if err != nil {
@@ -419,6 +472,51 @@ func (impl DbPipelineOrchestratorImpl) DeleteCiPipeline(pipeline *pipelineConfig
 		}
 	}
 	err = impl.ciPipelineMaterialRepository.Update(tx, materials...)
+
+	if !request.CiPipeline.IsDockerConfigOverridden {
+
+		CiTemplateBean := bean2.CiTemplateBean{
+			CiTemplate:         nil,
+			CiTemplateOverride: &pipelineConfig.CiTemplateOverride{},
+			CiBuildConfig:      request.CiPipeline.DockerConfigOverride.CiBuildConfig,
+			UserId:             userId,
+		}
+
+		err := impl.ciPipelineHistoryService.SaveHistory(p, materials, &CiTemplateBean, repository4.TRIGGER_DELETE)
+
+		if err != nil {
+			impl.logger.Errorw("error in saving delete history for ci pipeline material and ci template overridden")
+		}
+
+	} else {
+		CiTemplateBean := bean2.CiTemplateBean{
+			CiTemplate: nil,
+			CiTemplateOverride: &pipelineConfig.CiTemplateOverride{
+				CiPipelineId:     request.CiPipeline.Id,
+				DockerRegistryId: request.CiPipeline.DockerConfigOverride.DockerRegistry,
+				DockerRepository: request.CiPipeline.DockerConfigOverride.DockerRepository,
+				//DockerfilePath:   ciPipeline.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)
+
+		if err != nil {
+			impl.logger.Errorw("error in saving delete history for ci pipeline material and ci template overridden")
+		}
+
+	}
+
 	return err
 }
 
@@ -560,7 +658,29 @@ func (impl DbPipelineOrchestratorImpl) CreateCiConf(createRequest *bean.CiConfig
 			if err != nil {
 				return nil, err
 			}
+
+			err = impl.ciPipelineHistoryService.SaveHistory(ciPipelineObject, pipelineMaterials, ciTemplateBean, repository4.TRIGGER_ADD)
+
+			if err != nil {
+				impl.logger.Errorw("error in saving history for ci pipeline")
+			}
+
+		} else {
+
+			ciTemplateBean := &bean2.CiTemplateBean{
+				CiTemplateOverride: &pipelineConfig.CiTemplateOverride{},
+				CiBuildConfig:      nil,
+				UserId:             createRequest.UserId,
+			}
+
+			err = impl.ciPipelineHistoryService.SaveHistory(ciPipelineObject, pipelineMaterials, ciTemplateBean, repository4.TRIGGER_ADD)
+
+			if err != nil {
+				impl.logger.Errorw("error in saving history for ci pipeline")
+			}
+
 		}
+
 		//creating ci stages after tx commit due to FK constraints
 		if ciPipeline.PreBuildStage != nil && len(ciPipeline.PreBuildStage.Steps) > 0 {
 			//creating pre stage
@@ -781,11 +901,14 @@ func (impl DbPipelineOrchestratorImpl) DeleteApp(appId int, userId int32) error
 		materials[i].UpdatedBy = userId
 	}
 	err = impl.materialRepository.Update(materials)
+
 	if err != nil {
 		impl.logger.Errorw("could not delete materials ", "err", err)
 		return err
 	}
 
+	err = impl.gitMaterialHistoryService.CreateDeleteMaterialHistory(materials)
+
 	impl.logger.Debug("deleting materials in git_sensor")
 	for _, m := range materials {
 		err = impl.updateRepositoryToGitSensor(m)
@@ -1029,10 +1152,14 @@ func (impl DbPipelineOrchestratorImpl) updateMaterial(updateMaterialDTO *bean.Up
 	currentMaterial.AuditLog = sql.AuditLog{UpdatedBy: updateMaterialDTO.UserId, CreatedBy: currentMaterial.CreatedBy, UpdatedOn: time.Now(), CreatedOn: currentMaterial.CreatedOn}
 
 	err = impl.materialRepository.UpdateMaterial(currentMaterial)
+
 	if err != nil {
 		impl.logger.Errorw("error in updating material", "material", currentMaterial, "err", err)
 		return nil, err
 	}
+
+	err = impl.gitMaterialHistoryService.CreateMaterialHistory(currentMaterial)
+
 	return currentMaterial, nil
 }
 
@@ -1050,10 +1177,14 @@ func (impl DbPipelineOrchestratorImpl) createMaterial(inputMaterial *bean.GitMat
 		AuditLog:        sql.AuditLog{UpdatedBy: userId, CreatedBy: userId, UpdatedOn: time.Now(), CreatedOn: time.Now()},
 	}
 	err := impl.materialRepository.SaveMaterial(material)
+
 	if err != nil {
 		impl.logger.Errorw("error in saving material", "material", material, "err", err)
 		return nil, err
 	}
+
+	err = impl.gitMaterialHistoryService.CreateMaterialHistory(material)
+
 	return material, err
 }
 
diff --git a/pkg/pipeline/PipelineBuilder.go b/pkg/pipeline/PipelineBuilder.go
index c003f814a..a036c8fd1 100644
--- a/pkg/pipeline/PipelineBuilder.go
+++ b/pkg/pipeline/PipelineBuilder.go
@@ -156,8 +156,12 @@ type PipelineBuilderImpl struct {
 	ciPipelineMaterialRepository     pipelineConfig.CiPipelineMaterialRepository
 	//ciTemplateOverrideRepository     pipelineConfig.CiTemplateOverrideRepository
 	//ciBuildConfigService CiBuildConfigService
-	ciTemplateService CiTemplateService
-	userService       user.UserService
+	ciTemplateService            CiTemplateService
+	userService                  user.UserService
+	ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository
+	gitMaterialHistoryService    history.GitMaterialHistoryService
+	CiTemplateHistoryService     history.CiTemplateHistoryService
+	CiPipelineHistoryService     history.CiPipelineHistoryService
 }
 
 func NewPipelineBuilderImpl(logger *zap.SugaredLogger,
@@ -196,7 +200,11 @@ func NewPipelineBuilderImpl(logger *zap.SugaredLogger,
 	deploymentGroupRepository repository.DeploymentGroupRepository,
 	ciPipelineMaterialRepository pipelineConfig.CiPipelineMaterialRepository,
 	userService user.UserService,
-	ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository, ciTemplateService CiTemplateService) *PipelineBuilderImpl {
+	ciTemplateService CiTemplateService,
+	ciTemplateOverrideRepository pipelineConfig.CiTemplateOverrideRepository,
+	gitMaterialHistoryService history.GitMaterialHistoryService,
+	CiTemplateHistoryService history.CiTemplateHistoryService,
+	CiPipelineHistoryService history.CiPipelineHistoryService) *PipelineBuilderImpl {
 	return &PipelineBuilderImpl{
 		logger:                        logger,
 		dbPipelineOrchestrator:        dbPipelineOrchestrator,
@@ -240,7 +248,11 @@ func NewPipelineBuilderImpl(logger *zap.SugaredLogger,
 		ciTemplateService:                ciTemplateService,
 		//ciTemplateOverrideRepository:     ciTemplateOverrideRepository,
 		//ciBuildConfigService: ciBuildConfigService,
-		userService: userService,
+		userService:                  userService,
+		ciTemplateOverrideRepository: ciTemplateOverrideRepository,
+		gitMaterialHistoryService:    gitMaterialHistoryService,
+		CiTemplateHistoryService:     CiTemplateHistoryService,
+		CiPipelineHistoryService:     CiPipelineHistoryService,
 	}
 }
 
@@ -311,11 +323,15 @@ func (impl PipelineBuilderImpl) DeleteMaterial(request *bean.UpdateMaterialDTO)
 	}
 	existingMaterial.UpdatedOn = time.Now()
 	existingMaterial.UpdatedBy = request.UserId
+
 	err = impl.materialRepo.MarkMaterialDeleted(existingMaterial)
+
 	if err != nil {
 		impl.logger.Errorw("error in deleting git material", "gitMaterial", existingMaterial)
 		return err
 	}
+	err = impl.gitMaterialHistoryService.MarkMaterialDeletedAndCreateHistory(existingMaterial)
+
 	return nil
 }
 
@@ -429,6 +445,10 @@ func (impl PipelineBuilderImpl) getCiTemplateVariables(appId int) (ciConfig *bea
 		Version:        template.Version,
 		CiTemplateName: template.TemplateName,
 		Materials:      materials,
+		UpdatedOn:      template.UpdatedOn,
+		UpdatedBy:      template.UpdatedBy,
+		CreatedBy:      template.CreatedBy,
+		CreatedOn:      template.CreatedOn,
 	}
 	return ciConfig, err
 }
@@ -679,6 +699,7 @@ func (impl PipelineBuilderImpl) UpdateCiTemplate(updateRequest *bean.CiConfigReq
 		GitMaterialId: ciBuildConfig.GitMaterialId,
 		//Args:              string(argByte),
 		//TargetPlatform:    originalCiConf.DockerBuildConfig.TargetPlatform,
+		AppId:             originalCiConf.AppId,
 		BeforeDockerBuild: string(beforeByte),
 		AfterDockerBuild:  string(afterByte),
 		Version:           originalCiConf.Version,
@@ -686,6 +707,12 @@ func (impl PipelineBuilderImpl) UpdateCiTemplate(updateRequest *bean.CiConfigReq
 		DockerRepository:  originalCiConf.DockerRepository,
 		DockerRegistryId:  originalCiConf.DockerRegistry,
 		Active:            true,
+		AuditLog: sql.AuditLog{
+			CreatedOn: originalCiConf.CreatedOn,
+			CreatedBy: originalCiConf.CreatedBy,
+			UpdatedOn: time.Now(),
+			UpdatedBy: originalCiConf.UpdatedBy,
+		},
 	}
 
 	ciBuildConfig.Id = originalCiBuildConfig.Id
@@ -698,7 +725,15 @@ func (impl PipelineBuilderImpl) UpdateCiTemplate(updateRequest *bean.CiConfigReq
 	if err != nil {
 		return nil, err
 	}
+
 	originalCiConf.CiBuildConfig = ciBuildConfig
+
+	err = impl.CiTemplateHistoryService.SaveHistory(ciTemplateBean, "update")
+
+	if err != nil {
+		impl.logger.Errorw("error in saving update history for ci template", "error", err)
+	}
+
 	return originalCiConf, nil
 }
 
@@ -784,6 +819,13 @@ func (impl PipelineBuilderImpl) CreateCiPipeline(createRequest *bean.CiConfigReq
 	}
 
 	//-- template config end
+
+	err = impl.CiTemplateHistoryService.SaveHistory(ciTemplateBean, "add")
+
+	if err != nil {
+		impl.logger.Errorw("error in saving audit logs of ci Template", "error", err)
+	}
+
 	createRequest.Id = ciTemplate.Id
 	createRequest.CiTemplateName = ciTemplate.TemplateName
 	if len(createRequest.CiPipelines) > 0 {
@@ -1029,7 +1071,7 @@ func (impl PipelineBuilderImpl) DeleteCiPipeline(request *bean.CiPatchRequest) (
 	// Rollback tx on error.
 	defer tx.Rollback()
 
-	err = impl.dbPipelineOrchestrator.DeleteCiPipeline(pipeline, request.UserId, tx)
+	err = impl.dbPipelineOrchestrator.DeleteCiPipeline(pipeline, request, tx)
 	if err != nil {
 		impl.logger.Errorw("error in deleting pipeline db")
 		return nil, err
@@ -1093,7 +1135,7 @@ func (impl PipelineBuilderImpl) patchCiPipelineUpdateSource(baseCiConfig *bean.C
 		return nil, fmt.Errorf("update of plugin scm material not supported")
 	} else {
 		modifiedCiPipeline.ScanEnabled = baseCiConfig.ScanEnabled
-		modifiedCiPipeline, err = impl.dbPipelineOrchestrator.PatchMaterialValue(modifiedCiPipeline, baseCiConfig.UserId)
+		modifiedCiPipeline, err = impl.dbPipelineOrchestrator.PatchMaterialValue(modifiedCiPipeline, baseCiConfig.UserId, pipeline)
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/pipeline/history/CiPipelineHistoryService.go b/pkg/pipeline/history/CiPipelineHistoryService.go
new file mode 100644
index 000000000..3811e5895
--- /dev/null
+++ b/pkg/pipeline/history/CiPipelineHistoryService.go
@@ -0,0 +1,97 @@
+package history
+
+import (
+	"encoding/json"
+	"github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig"
+	"github.com/devtron-labs/devtron/pkg/pipeline/bean"
+	"github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"go.uber.org/zap"
+	"time"
+)
+
+type CiPipelineHistoryService interface {
+	SaveHistory(pipeline *pipelineConfig.CiPipeline, CiPipelineMaterial []*pipelineConfig.CiPipelineMaterial, ciTemplateBean *bean.CiTemplateBean, Trigger string) error
+}
+
+type CiPipelineHistoryServiceImpl struct {
+	CiPipelineHistoryRepository repository.CiPipelineHistoryRepository
+	logger                      *zap.SugaredLogger
+}
+
+func NewCiPipelineHistoryServiceImpl(CiPipelineHistoryRepository repository.CiPipelineHistoryRepository,
+	logger *zap.SugaredLogger) *CiPipelineHistoryServiceImpl {
+	return &CiPipelineHistoryServiceImpl{
+		CiPipelineHistoryRepository: CiPipelineHistoryRepository,
+		logger:                      logger,
+	}
+}
+
+func (impl *CiPipelineHistoryServiceImpl) SaveHistory(pipeline *pipelineConfig.CiPipeline, CiPipelineMaterial []*pipelineConfig.CiPipelineMaterial, CiTemplateBean *bean.CiTemplateBean, Trigger string) error {
+
+	CiPipelineMaterialJson, _ := json.Marshal(CiPipelineMaterial)
+
+	var CiPipelineHistory repository.CiPipelineHistory
+	var CiTemplateOverride repository.CiPipelineTemplateOverrideHistoryDTO
+
+	IsDockerConfigOverriden := pipeline.IsDockerConfigOverridden
+
+	if IsDockerConfigOverriden {
+		ciTemplateId := 0
+		ciTemplateOverrideId := 0
+
+		CiBuildConfigDbEntity, _ := bean.ConvertBuildConfigBeanToDbEntity(ciTemplateId, ciTemplateOverrideId, CiTemplateBean.CiBuildConfig, CiTemplateBean.UserId)
+
+		CiTemplateOverride = repository.CiPipelineTemplateOverrideHistoryDTO{
+			DockerRegistryId:      CiTemplateBean.CiTemplateOverride.DockerRegistryId,
+			DockerRepository:      CiTemplateBean.CiTemplateOverride.DockerRepository,
+			DockerfilePath:        CiTemplateBean.CiTemplateOverride.DockerfilePath,
+			Active:                CiTemplateBean.CiTemplateOverride.Active,
+			CiBuildConfigId:       CiBuildConfigDbEntity.Id,
+			BuildMetaDataType:     CiBuildConfigDbEntity.Type,
+			BuildMetadata:         CiBuildConfigDbEntity.BuildMetadata,
+			AuditLog:              CiTemplateBean.CiTemplateOverride.AuditLog,
+			IsCiTemplateOverriden: true,
+		}
+
+	} else {
+
+		CiTemplateOverride = repository.CiPipelineTemplateOverrideHistoryDTO{
+			DockerRegistryId:  "",
+			DockerRepository:  "",
+			DockerfilePath:    "",
+			Active:            false,
+			CiBuildConfigId:   0,
+			BuildMetaDataType: "",
+			BuildMetadata:     "",
+			AuditLog: sql.AuditLog{
+				CreatedOn: time.Now(),
+				CreatedBy: CiTemplateBean.UserId,
+				UpdatedOn: time.Now(),
+				UpdatedBy: CiTemplateBean.UserId,
+			},
+			IsCiTemplateOverriden: false,
+		}
+
+	}
+	CiTemplateOverrideJson, _ := json.Marshal(CiTemplateOverride)
+
+	CiPipelineHistory = repository.CiPipelineHistory{
+		CiPipelineId:              pipeline.Id,
+		CiTemplateOverrideHistory: string(CiTemplateOverrideJson),
+		CiPipelineMaterialHistory: string(CiPipelineMaterialJson),
+		Trigger:                   Trigger,
+		ScanEnabled:               pipeline.ScanEnabled,
+		Manual:                    pipeline.IsManual,
+	}
+
+	err := impl.CiPipelineHistoryRepository.Save(&CiPipelineHistory)
+
+	if err != nil {
+		impl.logger.Errorw("error in saving history of ci pipeline")
+		return err
+	}
+
+	return nil
+
+}
diff --git a/pkg/pipeline/history/GitMaterialHistoryService.go b/pkg/pipeline/history/GitMaterialHistoryService.go
new file mode 100644
index 000000000..c40fcfeef
--- /dev/null
+++ b/pkg/pipeline/history/GitMaterialHistoryService.go
@@ -0,0 +1,101 @@
+package history
+
+import (
+	"github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig"
+	"github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"go.uber.org/zap"
+)
+
+type GitMaterialHistoryService interface {
+	CreateMaterialHistory(inputMaterial *pipelineConfig.GitMaterial) error
+	CreateDeleteMaterialHistory(materials []*pipelineConfig.GitMaterial) error
+	MarkMaterialDeletedAndCreateHistory(material *pipelineConfig.GitMaterial) error
+}
+
+type GitMaterialHistoryServiceImpl struct {
+	gitMaterialHistoryRepository repository.GitMaterialHistoryRepository
+	logger                       *zap.SugaredLogger
+}
+
+func NewGitMaterialHistoryServiceImpl(gitMaterialHistoryRepository repository.GitMaterialHistoryRepository,
+	logger *zap.SugaredLogger) *GitMaterialHistoryServiceImpl {
+
+	return &GitMaterialHistoryServiceImpl{
+		gitMaterialHistoryRepository: gitMaterialHistoryRepository,
+		logger:                       logger,
+	}
+}
+
+func (impl GitMaterialHistoryServiceImpl) CreateMaterialHistory(inputMaterial *pipelineConfig.GitMaterial) error {
+
+	material := &repository.GitMaterialHistory{
+		GitMaterialId:   inputMaterial.Id,
+		Url:             inputMaterial.Url,
+		AppId:           inputMaterial.AppId,
+		Name:            inputMaterial.Name,
+		GitProviderId:   inputMaterial.GitProviderId,
+		Active:          inputMaterial.Active,
+		CheckoutPath:    inputMaterial.CheckoutPath,
+		FetchSubmodules: inputMaterial.FetchSubmodules,
+		AuditLog:        sql.AuditLog{UpdatedBy: inputMaterial.UpdatedBy, CreatedBy: inputMaterial.CreatedBy, UpdatedOn: inputMaterial.UpdatedOn, CreatedOn: inputMaterial.CreatedOn},
+	}
+
+	err := impl.gitMaterialHistoryRepository.SaveGitMaterialHistory(material)
+
+	if err != nil {
+		impl.logger.Errorw("error in saving create/update history for git repository")
+	}
+
+	return nil
+
+}
+
+func (impl GitMaterialHistoryServiceImpl) CreateDeleteMaterialHistory(materials []*pipelineConfig.GitMaterial) error {
+
+	materialsHistory := []*repository.GitMaterialHistory{}
+
+	for _, material := range materials {
+
+		materialHistory := repository.GitMaterialHistory{
+			GitMaterialId:   material.Id,
+			AppId:           material.AppId,
+			GitProviderId:   material.GitProviderId,
+			Active:          material.Active,
+			Url:             material.Url,
+			Name:            material.Name,
+			CheckoutPath:    material.CheckoutPath,
+			FetchSubmodules: material.FetchSubmodules,
+			AuditLog: sql.AuditLog{
+				CreatedOn: material.CreatedOn,
+				CreatedBy: material.CreatedBy,
+				UpdatedOn: material.UpdatedOn,
+				UpdatedBy: material.UpdatedBy,
+			},
+		}
+		materialsHistory = append(materialsHistory, &materialHistory)
+	}
+
+	err := impl.gitMaterialHistoryRepository.SaveDeleteMaterialHistory(materialsHistory)
+
+	if err != nil {
+		impl.logger.Errorw("Error in saving delete history for git material Repository")
+	}
+
+	return nil
+
+}
+
+func (impl GitMaterialHistoryServiceImpl) MarkMaterialDeletedAndCreateHistory(material *pipelineConfig.GitMaterial) error {
+
+	material.Active = false
+
+	err := impl.CreateMaterialHistory(material)
+
+	if err != nil {
+		impl.logger.Errorw("error in saving delete history for git material repository")
+	}
+
+	return nil
+
+}
diff --git a/pkg/pipeline/history/ciTemplateHistoryService.go b/pkg/pipeline/history/ciTemplateHistoryService.go
new file mode 100644
index 000000000..44d3704e1
--- /dev/null
+++ b/pkg/pipeline/history/ciTemplateHistoryService.go
@@ -0,0 +1,71 @@
+package history
+
+import (
+	"github.com/devtron-labs/devtron/pkg/pipeline/bean"
+	"github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"go.uber.org/zap"
+)
+
+type CiTemplateHistoryService interface {
+	SaveHistory(material *bean.CiTemplateBean, trigger string) error
+}
+
+type CiTemplateHistoryServiceImpl struct {
+	CiTemplateHistoryRepository repository.CiTemplateHistoryRepository
+	logger                      *zap.SugaredLogger
+}
+
+func NewCiTemplateHistoryServiceImpl(CiTemplateHistoryRepository repository.CiTemplateHistoryRepository,
+	logger *zap.SugaredLogger) *CiTemplateHistoryServiceImpl {
+
+	return &CiTemplateHistoryServiceImpl{
+		CiTemplateHistoryRepository: CiTemplateHistoryRepository,
+		logger:                      logger,
+	}
+}
+
+func (impl CiTemplateHistoryServiceImpl) SaveHistory(ciTemplateBean *bean.CiTemplateBean, trigger string) error {
+
+	ciTemplate := ciTemplateBean.CiTemplate
+	ciBuildConfig := ciTemplateBean.CiBuildConfig
+
+	ciTemplateId := 0
+	ciTemplateOverrideId := 0
+
+	ciBuildConfigDbEntity, err := bean.ConvertBuildConfigBeanToDbEntity(ciTemplateId, ciTemplateOverrideId, ciBuildConfig, ciTemplateBean.UserId)
+
+	materialHistory := &repository.CiTemplateHistory{
+		CiTemplateId:       ciTemplate.Id,
+		AppId:              ciTemplate.AppId,
+		DockerRegistryId:   ciTemplate.DockerRegistryId,
+		DockerRepository:   ciTemplate.DockerRepository,
+		DockerfilePath:     ciTemplate.DockerfilePath, //in
+		Args:               ciTemplate.Args,
+		TargetPlatform:     ciTemplate.TargetPlatform,
+		BeforeDockerBuild:  ciTemplate.BeforeDockerBuild,
+		AfterDockerBuild:   ciTemplate.AfterDockerBuild,
+		TemplateName:       ciTemplate.TemplateName,
+		Version:            ciTemplate.Version,
+		Active:             ciTemplate.Active,
+		GitMaterialId:      ciTemplate.GitMaterialId,
+		DockerBuildOptions: ciTemplate.DockerBuildOptions,
+		App:                ciTemplate.App,
+		DockerRegistry:     ciTemplate.DockerRegistry,
+		CiBuildConfigId:    ciBuildConfigDbEntity.Id,
+		BuildMetaDataType:  ciBuildConfigDbEntity.Type,
+		BuildMetadata:      ciBuildConfigDbEntity.BuildMetadata,
+		Trigger:            trigger,
+		AuditLog:           sql.AuditLog{CreatedOn: ciTemplate.CreatedOn, CreatedBy: ciTemplate.CreatedBy, UpdatedBy: ciTemplate.UpdatedBy, UpdatedOn: ciTemplate.UpdatedOn},
+	}
+
+	err = impl.CiTemplateHistoryRepository.Save(materialHistory)
+
+	if err != nil {
+		impl.logger.Errorw("unable to save history for ci template repository", "error", err)
+		return err
+	}
+
+	return nil
+
+}
diff --git a/pkg/pipeline/history/repository/CiPipelineHistoryRepository.go b/pkg/pipeline/history/repository/CiPipelineHistoryRepository.go
new file mode 100644
index 000000000..c9bbea520
--- /dev/null
+++ b/pkg/pipeline/history/repository/CiPipelineHistoryRepository.go
@@ -0,0 +1,102 @@
+package repository
+
+import (
+	"github.com/devtron-labs/devtron/pkg/bean"
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"github.com/go-pg/pg"
+	"go.uber.org/zap"
+)
+
+type CiPipelineTemplateOverrideHistoryDTO struct {
+	DockerRegistryId      string `json:"docker_registry_id"`
+	DockerRepository      string `json:"docker_repository"`
+	DockerfilePath        string `json:"dockerfile_path"`
+	Active                bool   `json:"active,notnull"`
+	CiBuildConfigId       int    `json:"ci_build_config_id"`
+	BuildMetaDataType     string `json:"build_meta_data_type"`
+	BuildMetadata         string `json:"build_metadata"`
+	IsCiTemplateOverriden bool   `json:"is_ci_template_overriden"`
+	sql.AuditLog
+}
+
+type CiPipelineMaterialHistoryDTO struct {
+	tableName     struct{} `json:"ci_pipeline_material"`
+	Id            int      `json:"id"`
+	GitMaterialId int      `json:"git_material_id"` //id stored in db GitMaterial( foreign key)
+	CiPipelineId  int      `json:"ci_pipeline_id"`
+	Path          string   `json:"path"` // defaults to root of git repo
+	//depricated was used in gocd remove this
+	CheckoutPath string          `json:"checkout_path"` //path where code will be checked out for single source `./` default for multiSource configured by user
+	Type         bean.SourceType `json:"type"`
+	Value        string          `json:"value"`
+	ScmId        string          `json:"scm_id"`      //id of gocd object
+	ScmName      string          `json:"scm_name"`    //gocd scm name
+	ScmVersion   string          `json:"scm_version"` //gocd scm version
+	Active       bool            `json:"active,notnull"`
+	Regex        string          `json:"regex"`
+	GitTag       string          `json:"-"`
+	sql.AuditLog
+}
+
+//type CiPipelineMaterialHistoryDTO struct {
+//	PipelineMaterialId string `sql:"ci_material_id"`
+//	GitMaterialId      int    `sql:"git_material_id"` //id stored in db GitMaterial( foreign key)
+//	Path               string `sql:"path"`            // defaults to root of git repo
+//	//depricated was used in gocd remove this
+//	CheckoutPath string          `sql:"checkout_path"` //path where code will be checked out for single source `./` default for multiSource configured by user
+//	Type         bean.SourceType `sql:"type"`
+//	Value        string          `sql:"value"`
+//	ScmId        string          `sql:"scm_id"`      //id of gocd object
+//	ScmName      string          `sql:"scm_name"`    //gocd scm name
+//	ScmVersion   string          `sql:"scm_version"` //gocd scm version
+//	Regex        string          `json:"regex"`
+//	GitTag       string          `sql:"-"`
+//	Active       bool   		 `sql:"active,notnull"`
+//	sql.AuditLog
+//}
+
+type CiPipelineHistory struct {
+	tableName                 struct{} `sql:"ci_pipeline_history" pg:",discard_unknown_columns"`
+	Id                        int      `sql:"id,pk"`
+	CiPipelineId              int      `sql:"ci_pipeline_id"`
+	CiTemplateOverrideHistory string   `sql:"ci_template_override_history"`
+	CiPipelineMaterialHistory string   `sql:"ci_pipeline_material_history"`
+	Trigger                   string   `sql:"trigger"`
+	ScanEnabled               bool     `sql:"scan_enabled,notnull"`
+	Manual                    bool     `sql:"manual,notnull"`
+}
+
+const (
+	TRIGGER_ADD    = "add"
+	TRIGGER_UPDATE = "update"
+	TRIGGER_DELETE = "delete"
+)
+
+type CiPipelineHistoryRepository interface {
+	Save(ciPipelineHistory *CiPipelineHistory) error
+}
+
+type CiPipelineHistoryRepositoryImpl struct {
+	dbConnection *pg.DB
+	logger       *zap.SugaredLogger
+}
+
+func NewCiPipelineHistoryRepositoryImpl(dbConnection *pg.DB, logger *zap.SugaredLogger) *CiPipelineHistoryRepositoryImpl {
+
+	return &CiPipelineHistoryRepositoryImpl{
+		dbConnection: dbConnection,
+		logger:       logger,
+	}
+}
+
+func (impl *CiPipelineHistoryRepositoryImpl) Save(CiPipelineHistory *CiPipelineHistory) error {
+
+	err := impl.dbConnection.Insert(CiPipelineHistory)
+
+	if err != nil {
+		impl.logger.Errorw("error in saving history for ci pipeline")
+		return err
+	}
+
+	return nil
+}
diff --git a/pkg/pipeline/history/repository/GitMaterialHistoryRepository.go b/pkg/pipeline/history/repository/GitMaterialHistoryRepository.go
new file mode 100644
index 000000000..cc3cb22c9
--- /dev/null
+++ b/pkg/pipeline/history/repository/GitMaterialHistoryRepository.go
@@ -0,0 +1,53 @@
+package repository
+
+import (
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"github.com/go-pg/pg"
+)
+
+type GitMaterialHistory struct {
+	tableName       struct{} `sql:"git_material_history" pg:",discard_unknown_columns"`
+	Id              int      `sql:"id,pk"`
+	GitMaterialId   int      `sql:"git_material_id"`
+	AppId           int      `sql:"app_id,notnull"`
+	GitProviderId   int      `sql:"git_provider_id,notnull"`
+	Active          bool     `sql:"active,notnull"`
+	Url             string   `sql:"url,omitempty"`
+	Name            string   `sql:"name, omitempty"`
+	CheckoutPath    string   `sql:"checkout_path, omitempty"`
+	FetchSubmodules bool     `sql:"fetch_submodules,notnull"`
+	sql.AuditLog
+}
+
+type GitMaterialHistoryRepository interface {
+	SaveGitMaterialHistory(material *GitMaterialHistory) error
+	SaveDeleteMaterialHistory(materials []*GitMaterialHistory) error
+}
+
+type GitMaterialHistoryRepositoryImpl struct {
+	dbConnection *pg.DB
+}
+
+func NewGitMaterialHistoryRepositoyImpl(dbConnection *pg.DB) *GitMaterialHistoryRepositoryImpl {
+	return &GitMaterialHistoryRepositoryImpl{
+		dbConnection: dbConnection,
+	}
+}
+
+func (repo GitMaterialHistoryRepositoryImpl) SaveGitMaterialHistory(material *GitMaterialHistory) error {
+	return repo.dbConnection.Insert(material)
+}
+
+func (repo GitMaterialHistoryRepositoryImpl) SaveDeleteMaterialHistory(materials []*GitMaterialHistory) error {
+
+	err := repo.dbConnection.RunInTransaction(func(tx *pg.Tx) error {
+		for _, material := range materials {
+			_, err := tx.Model(material).WherePK().Insert()
+			if err != nil {
+				return err
+			}
+		}
+		return nil
+	})
+	return err
+}
diff --git a/pkg/pipeline/history/repository/ciTemplateHistoryRepository.go b/pkg/pipeline/history/repository/ciTemplateHistoryRepository.go
new file mode 100644
index 000000000..07f53b367
--- /dev/null
+++ b/pkg/pipeline/history/repository/ciTemplateHistoryRepository.go
@@ -0,0 +1,64 @@
+package repository
+
+import (
+	"github.com/devtron-labs/devtron/internal/sql/repository/app"
+	repository2 "github.com/devtron-labs/devtron/internal/sql/repository/dockerRegistry"
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"github.com/go-pg/pg"
+	"go.uber.org/zap"
+)
+
+type CiTemplateHistory struct {
+	tableName          struct{} `sql:"ci_template_history" pg:",discard_unknown_columns"`
+	Id                 int      `sql:"id,pk"`
+	CiTemplateId       int      `sql:"ci_template_id"`
+	AppId              int      `sql:"app_id"`             //foreign key of app
+	DockerRegistryId   string   `sql:"docker_registry_id"` //foreign key of registry
+	DockerRepository   string   `sql:"docker_repository"`
+	DockerfilePath     string   `sql:"dockerfile_path"`
+	Args               string   `sql:"args"` //json string format of map[string]string
+	TargetPlatform     string   `sql:"target_platform,notnull"`
+	BeforeDockerBuild  string   `sql:"before_docker_build"` //json string  format of []*Task
+	AfterDockerBuild   string   `sql:"after_docker_build"`  //json string  format of []*Task
+	TemplateName       string   `sql:"template_name"`
+	Version            string   `sql:"version"` //gocd etage
+	Active             bool     `sql:"active,notnull"`
+	GitMaterialId      int      `sql:"git_material_id"`
+	DockerBuildOptions string   `sql:"docker_build_options"` //json string format of map[string]string
+	CiBuildConfigId    int      `sql:"ci_build_config_id"`
+	BuildMetaDataType  string   `sql:"build_meta_data_type"`
+	BuildMetadata      string   `sql:"build_metadata"`
+	Trigger            string   `sql:"trigger"`
+	sql.AuditLog
+	App            *app.App
+	DockerRegistry *repository2.DockerArtifactStore
+}
+
+type CiTemplateHistoryRepository interface {
+	Save(material *CiTemplateHistory) error
+}
+
+type CiTemplateHistoryRepositoryImpl struct {
+	dbConnection *pg.DB
+	logger       *zap.SugaredLogger
+}
+
+func NewCiTemplateHistoryRepositoryImpl(dbConnection *pg.DB, logger *zap.SugaredLogger) *CiTemplateHistoryRepositoryImpl {
+	return &CiTemplateHistoryRepositoryImpl{
+		dbConnection: dbConnection,
+		logger:       logger,
+	}
+}
+
+func (impl CiTemplateHistoryRepositoryImpl) Save(material *CiTemplateHistory) error {
+
+	err := impl.dbConnection.Insert(material)
+
+	if err != nil {
+		impl.logger.Errorw("error in saving history for ci template history")
+		return err
+	}
+
+	return nil
+
+}
diff --git a/pkg/pipeline/history/repository/mocks/CiPipelineHistoryRepository.go b/pkg/pipeline/history/repository/mocks/CiPipelineHistoryRepository.go
new file mode 100644
index 000000000..3bd9880cc
--- /dev/null
+++ b/pkg/pipeline/history/repository/mocks/CiPipelineHistoryRepository.go
@@ -0,0 +1,42 @@
+// Code generated by mockery v2.14.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	repository "github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// CiPipelineHistoryRepository is an autogenerated mock type for the CiPipelineHistoryRepository type
+type CiPipelineHistoryRepository struct {
+	mock.Mock
+}
+
+// Save provides a mock function with given fields: ciPipelineHistory
+func (_m *CiPipelineHistoryRepository) Save(ciPipelineHistory *repository.CiPipelineHistory) error {
+	ret := _m.Called(ciPipelineHistory)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*repository.CiPipelineHistory) error); ok {
+		r0 = rf(ciPipelineHistory)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+type mockConstructorTestingTNewCiPipelineHistoryRepository interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewCiPipelineHistoryRepository creates a new instance of CiPipelineHistoryRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewCiPipelineHistoryRepository(t mockConstructorTestingTNewCiPipelineHistoryRepository) *CiPipelineHistoryRepository {
+	mock := &CiPipelineHistoryRepository{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/pkg/pipeline/history/repository/mocks/CiTemplateHistoryRepository.go b/pkg/pipeline/history/repository/mocks/CiTemplateHistoryRepository.go
new file mode 100644
index 000000000..709c0ea37
--- /dev/null
+++ b/pkg/pipeline/history/repository/mocks/CiTemplateHistoryRepository.go
@@ -0,0 +1,42 @@
+// Code generated by mockery v2.14.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	repository "github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// CiTemplateHistoryRepository is an autogenerated mock type for the CiTemplateHistoryRepository type
+type CiTemplateHistoryRepository struct {
+	mock.Mock
+}
+
+// Save provides a mock function with given fields: material
+func (_m *CiTemplateHistoryRepository) Save(material *repository.CiTemplateHistory) error {
+	ret := _m.Called(material)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*repository.CiTemplateHistory) error); ok {
+		r0 = rf(material)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+type mockConstructorTestingTNewCiTemplateHistoryRepository interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewCiTemplateHistoryRepository creates a new instance of CiTemplateHistoryRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewCiTemplateHistoryRepository(t mockConstructorTestingTNewCiTemplateHistoryRepository) *CiTemplateHistoryRepository {
+	mock := &CiTemplateHistoryRepository{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/pkg/pipeline/history/repository/mocks/GitMaterialHistoryRepository.go b/pkg/pipeline/history/repository/mocks/GitMaterialHistoryRepository.go
new file mode 100644
index 000000000..63eee2264
--- /dev/null
+++ b/pkg/pipeline/history/repository/mocks/GitMaterialHistoryRepository.go
@@ -0,0 +1,56 @@
+// Code generated by mockery v2.14.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	repository "github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// GitMaterialHistoryRepository is an autogenerated mock type for the GitMaterialHistoryRepository type
+type GitMaterialHistoryRepository struct {
+	mock.Mock
+}
+
+// SaveDeleteMaterialHistory provides a mock function with given fields: materials
+func (_m *GitMaterialHistoryRepository) SaveDeleteMaterialHistory(materials []*repository.GitMaterialHistory) error {
+	ret := _m.Called(materials)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func([]*repository.GitMaterialHistory) error); ok {
+		r0 = rf(materials)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// SaveGitMaterialHistory provides a mock function with given fields: material
+func (_m *GitMaterialHistoryRepository) SaveGitMaterialHistory(material *repository.GitMaterialHistory) error {
+	ret := _m.Called(material)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*repository.GitMaterialHistory) error); ok {
+		r0 = rf(material)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+type mockConstructorTestingTNewGitMaterialHistoryRepository interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewGitMaterialHistoryRepository creates a new instance of GitMaterialHistoryRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewGitMaterialHistoryRepository(t mockConstructorTestingTNewGitMaterialHistoryRepository) *GitMaterialHistoryRepository {
+	mock := &GitMaterialHistoryRepository{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/scripts/sql/95_create_ci_build_history.down.sql b/scripts/sql/95_create_ci_build_history.down.sql
new file mode 100644
index 000000000..423cc3d18
--- /dev/null
+++ b/scripts/sql/95_create_ci_build_history.down.sql
@@ -0,0 +1,3 @@
+DROP TABLE IF EXISTS "public"."git_material_history";
+DROP TABLE IF EXISTS "public"."ci_template_history";
+DROP TABLE IF EXISTS "public"."ci_pipeline_history";
\ No newline at end of file
diff --git a/scripts/sql/95_create_ci_build_history.up.sql b/scripts/sql/95_create_ci_build_history.up.sql
new file mode 100644
index 000000000..687ba9278
--- /dev/null
+++ b/scripts/sql/95_create_ci_build_history.up.sql
@@ -0,0 +1,86 @@
+CREATE SEQUENCE IF NOT EXISTS id_seq_git_material_history;
+
+
+CREATE TABLE public.git_material_history (
+     "id" integer  NOT NULL DEFAULT nextval('id_seq_git_material_history'::regclass),
+     "app_id" integer,
+     "git_provider_id" integer,
+     "git_material_id" integer,
+     "active" boolean NOT NULL,
+     "name" character varying(250),
+     "url" character varying(250),
+     "created_on" timestamp with time zone NOT NULL,
+     "created_by" integer NOT NULL,
+     "updated_on" timestamp with time zone NOT NULL,
+     "updated_by" integer NOT NULL,
+     "checkout_path" character varying(250),
+     "fetch_submodules" boolean NOT NULL,
+     PRIMARY KEY ("id"),
+     CONSTRAINT git_material_history_git_material_id_fkey
+         FOREIGN KEY(git_material_id)
+             REFERENCES public.git_material(id)
+);
+
+ALTER TABLE public.git_material_history OWNER TO postgres;
+
+
+CREATE SEQUENCE IF NOT EXISTS id_seq_ci_template_history;
+
+CREATE TABLE public.ci_template_history (
+    id integer NOT NULL DEFAULT nextval('id_seq_ci_template_history'::regclass),
+    ci_template_id integer,
+    app_id integer,
+    docker_registry_id character varying(250),
+    docker_repository character varying(250),
+    dockerfile_path character varying(250),
+    args text,
+    before_docker_build text,
+    after_docker_build text,
+    template_name character varying(250),
+    version character varying(250),
+    target_platform VARCHAR(1000) NOT NULL DEFAULT '',
+    docker_build_options text,
+    active boolean,
+    created_on timestamp with time zone NOT NULL,
+    created_by integer NOT NULL,
+    updated_on timestamp with time zone NOT NULL,
+    updated_by integer NOT NULL,
+    git_material_id integer,
+    ci_build_config_id   integer,
+    build_meta_data_type  varchar(100),
+    build_metadata       text,
+    trigger character varying(100),
+    PRIMARY KEY ("id"),
+    CONSTRAINT ci_template_history_ci_template_id_fkey
+        FOREIGN KEY (ci_template_id)
+            REFERENCES public.ci_template(id),
+    CONSTRAINT ci_template_history_docker_registry_id_fkey
+        FOREIGN KEY(docker_registry_id)
+            REFERENCES public.docker_artifact_store(id),
+    CONSTRAINT ci_template_history_app_id_fkey
+        FOREIGN KEY(app_id)
+            REFERENCES public.app(id),
+    CONSTRAINT ci_template_git_material_history_id_fkey
+        FOREIGN KEY(git_material_id)
+            REFERENCES public.git_material(id)
+);
+
+
+ALTER TABLE public.ci_template OWNER TO postgres;
+
+CREATE SEQUENCE IF NOT EXISTS id_seq_ci_pipeline_history;
+
+CREATE TABLE public.ci_pipeline_history(
+   id integer NOT NULL default nextval('id_seq_ci_pipeline_history'::regclass),
+   ci_pipeline_id integer,
+   ci_template_override_history text,
+   ci_pipeline_material_history text,
+   scan_enabled boolean,
+   manual boolean,
+   trigger character varying(100),
+   PRIMARY KEY ("id"),
+   CONSTRAINT ci_pipeline_history_ci_pipeline_id_fk
+       FOREIGN KEY (ci_pipeline_id)
+           REFERENCES public.ci_pipeline(id)
+);
+
diff --git a/wire_gen.go b/wire_gen.go
index 77226b7d5..cc207508a 100644
--- a/wire_gen.go
+++ b/wire_gen.go
@@ -1,7 +1,8 @@
 // Code generated by Wire. DO NOT EDIT.
 
 //go:generate go run github.com/google/wire/cmd/wire
-//+build !wireinject
+//go:build !wireinject
+// +build !wireinject
 
 package main
 
@@ -354,17 +355,23 @@ func InitializeApp() (*App, error) {
 	globalPluginRepositoryImpl := repository9.NewGlobalPluginRepository(sugaredLogger, db)
 	pipelineStageServiceImpl := pipeline.NewPipelineStageService(sugaredLogger, pipelineStageRepositoryImpl, globalPluginRepositoryImpl)
 	ciTemplateOverrideRepositoryImpl := pipelineConfig.NewCiTemplateOverrideRepositoryImpl(db, sugaredLogger)
+	gitMaterialHistoryRepositoryImpl := repository6.NewGitMaterialHistoryRepositoyImpl(db)
+	gitMaterialHistoryServiceImpl := history.NewGitMaterialHistoryServiceImpl(gitMaterialHistoryRepositoryImpl, sugaredLogger)
+	ciPipelineHistoryRepositoryImpl := repository6.NewCiPipelineHistoryRepositoryImpl(db, sugaredLogger)
+	ciPipelineHistoryServiceImpl := history.NewCiPipelineHistoryServiceImpl(ciPipelineHistoryRepositoryImpl, sugaredLogger)
 	ciBuildConfigRepositoryImpl := pipelineConfig.NewCiBuildConfigRepositoryImpl(db, sugaredLogger)
 	ciBuildConfigServiceImpl := pipeline.NewCiBuildConfigServiceImpl(sugaredLogger, ciBuildConfigRepositoryImpl)
 	ciTemplateRepositoryImpl := pipelineConfig.NewCiTemplateRepositoryImpl(db, sugaredLogger)
 	ciTemplateServiceImpl := pipeline.NewCiTemplateServiceImpl(sugaredLogger, ciBuildConfigServiceImpl, ciTemplateRepositoryImpl, ciTemplateOverrideRepositoryImpl)
-	dbPipelineOrchestratorImpl := pipeline.NewDbPipelineOrchestrator(appRepositoryImpl, sugaredLogger, materialRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, ciPipelineMaterialRepositoryImpl, gitSensorClientImpl, ciConfig, appWorkflowRepositoryImpl, environmentRepositoryImpl, attributesServiceImpl, appListingRepositoryImpl, appCrudOperationServiceImpl, userAuthServiceImpl, prePostCdScriptHistoryServiceImpl, prePostCiScriptHistoryServiceImpl, pipelineStageServiceImpl, ciTemplateOverrideRepositoryImpl, ciTemplateServiceImpl)
+	dbPipelineOrchestratorImpl := pipeline.NewDbPipelineOrchestrator(appRepositoryImpl, sugaredLogger, materialRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, ciPipelineMaterialRepositoryImpl, gitSensorClientImpl, ciConfig, appWorkflowRepositoryImpl, environmentRepositoryImpl, attributesServiceImpl, appListingRepositoryImpl, appCrudOperationServiceImpl, userAuthServiceImpl, prePostCdScriptHistoryServiceImpl, prePostCiScriptHistoryServiceImpl, pipelineStageServiceImpl, ciTemplateOverrideRepositoryImpl, gitMaterialHistoryServiceImpl, ciPipelineHistoryServiceImpl, ciTemplateServiceImpl)
 	propertiesConfigServiceImpl := pipeline.NewPropertiesConfigServiceImpl(sugaredLogger, envConfigOverrideRepositoryImpl, chartRepositoryImpl, utilMergeUtil, environmentRepositoryImpl, dbPipelineOrchestratorImpl, applicationServiceClientImpl, envLevelAppMetricsRepositoryImpl, appLevelMetricsRepositoryImpl, deploymentTemplateHistoryServiceImpl)
 	ecrConfig, err := pipeline.GetEcrConfig()
 	if err != nil {
 		return nil, err
 	}
-	pipelineBuilderImpl := pipeline.NewPipelineBuilderImpl(sugaredLogger, dbPipelineOrchestratorImpl, dockerArtifactStoreRepositoryImpl, materialRepositoryImpl, appRepositoryImpl, pipelineRepositoryImpl, propertiesConfigServiceImpl, ciTemplateRepositoryImpl, ciPipelineRepositoryImpl, applicationServiceClientImpl, chartRepositoryImpl, ciArtifactRepositoryImpl, ecrConfig, envConfigOverrideRepositoryImpl, environmentRepositoryImpl, pipelineConfigRepositoryImpl, utilMergeUtil, appWorkflowRepositoryImpl, ciConfig, cdWorkflowRepositoryImpl, appServiceImpl, imageScanResultRepositoryImpl, argoK8sClientImpl, gitFactory, attributesServiceImpl, acdAuthConfig, gitOpsConfigRepositoryImpl, pipelineStrategyHistoryServiceImpl, prePostCiScriptHistoryServiceImpl, prePostCdScriptHistoryServiceImpl, deploymentTemplateHistoryServiceImpl, appLevelMetricsRepositoryImpl, pipelineStageServiceImpl, chartRefRepositoryImpl, chartTemplateServiceImpl, chartServiceImpl, helmAppServiceImpl, deploymentGroupRepositoryImpl, ciPipelineMaterialRepositoryImpl, userServiceImpl, ciTemplateOverrideRepositoryImpl, ciTemplateServiceImpl)
+	ciTemplateHistoryRepositoryImpl := repository6.NewCiTemplateHistoryRepositoryImpl(db, sugaredLogger)
+	ciTemplateHistoryServiceImpl := history.NewCiTemplateHistoryServiceImpl(ciTemplateHistoryRepositoryImpl, sugaredLogger)
+	pipelineBuilderImpl := pipeline.NewPipelineBuilderImpl(sugaredLogger, dbPipelineOrchestratorImpl, dockerArtifactStoreRepositoryImpl, materialRepositoryImpl, appRepositoryImpl, pipelineRepositoryImpl, propertiesConfigServiceImpl, ciTemplateRepositoryImpl, ciPipelineRepositoryImpl, applicationServiceClientImpl, chartRepositoryImpl, ciArtifactRepositoryImpl, ecrConfig, envConfigOverrideRepositoryImpl, environmentRepositoryImpl, pipelineConfigRepositoryImpl, utilMergeUtil, appWorkflowRepositoryImpl, ciConfig, cdWorkflowRepositoryImpl, appServiceImpl, imageScanResultRepositoryImpl, argoK8sClientImpl, gitFactory, attributesServiceImpl, acdAuthConfig, gitOpsConfigRepositoryImpl, pipelineStrategyHistoryServiceImpl, prePostCiScriptHistoryServiceImpl, prePostCdScriptHistoryServiceImpl, deploymentTemplateHistoryServiceImpl, appLevelMetricsRepositoryImpl, pipelineStageServiceImpl, chartRefRepositoryImpl, chartTemplateServiceImpl, chartServiceImpl, helmAppServiceImpl, deploymentGroupRepositoryImpl, ciPipelineMaterialRepositoryImpl, userServiceImpl, ciTemplateServiceImpl, ciTemplateOverrideRepositoryImpl, gitMaterialHistoryServiceImpl, ciTemplateHistoryServiceImpl, ciPipelineHistoryServiceImpl)
 	dbMigrationServiceImpl := pipeline.NewDbMogrationService(sugaredLogger, dbMigrationConfigRepositoryImpl)
 	globalCMCSRepositoryImpl := repository.NewGlobalCMCSRepositoryImpl(sugaredLogger, db)
 	globalCMCSServiceImpl := pipeline.NewGlobalCMCSServiceImpl(sugaredLogger, globalCMCSRepositoryImpl)


Test patch:
diff --git a/pkg/pipeline/history/CiPipelineHistoryService_test.go b/pkg/pipeline/history/CiPipelineHistoryService_test.go
new file mode 100644
index 000000000..903981515
--- /dev/null
+++ b/pkg/pipeline/history/CiPipelineHistoryService_test.go
@@ -0,0 +1,120 @@
+package history
+
+import (
+	"encoding/json"
+	"github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig"
+	"github.com/devtron-labs/devtron/internal/util"
+	bean2 "github.com/devtron-labs/devtron/pkg/pipeline/bean"
+	"github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
+	"github.com/devtron-labs/devtron/pkg/pipeline/history/repository/mocks"
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestCiPipelineHistoryService(t *testing.T) {
+
+	t.Run("SaveHistory", func(t *testing.T) {
+
+		sugaredLogger, err := util.NewSugardLogger()
+		assert.Nil(t, err)
+
+		mockedCiPipelineHistoryRepository := mocks.NewCiPipelineHistoryRepository(t)
+
+		CiPipelineHistoryServiceImpl := NewCiPipelineHistoryServiceImpl(mockedCiPipelineHistoryRepository, sugaredLogger)
+
+		PipelineObject := pipelineConfig.CiPipeline{
+			Id:                       5,
+			AppId:                    2,
+			App:                      nil,
+			CiTemplateId:             3,
+			DockerArgs:               "",
+			Name:                     "",
+			Version:                  "",
+			Active:                   false,
+			Deleted:                  false,
+			IsManual:                 false,
+			IsExternal:               false,
+			ParentCiPipeline:         0,
+			ScanEnabled:              false,
+			IsDockerConfigOverridden: true,
+			AuditLog:                 sql.AuditLog{},
+		}
+
+		PipelineMaterialsObject := []*pipelineConfig.CiPipelineMaterial{&pipelineConfig.CiPipelineMaterial{
+			Id:            0,
+			GitMaterialId: 22,
+			CiPipelineId:  5,
+			Path:          "",
+			CheckoutPath:  "",
+			Type:          "",
+			Value:         "",
+			ScmId:         "",
+			ScmName:       "",
+			ScmVersion:    "",
+			Active:        false,
+			Regex:         "",
+			GitTag:        "",
+			AuditLog:      sql.AuditLog{}},
+		}
+
+		CiTemplateObject := bean2.CiTemplateBean{
+			CiTemplateOverride: &pipelineConfig.CiTemplateOverride{
+				Id:               0,
+				CiPipelineId:     5,
+				DockerRegistryId: "prakash",
+				DockerRepository: "prakash1001/sams-repository-3",
+				DockerfilePath:   "",
+				GitMaterialId:    22,
+				Active:           true,
+				CiBuildConfigId:  20,
+				AuditLog:         sql.AuditLog{},
+				GitMaterial:      nil,
+				DockerRegistry:   nil,
+				CiBuildConfig:    nil,
+			},
+			CiBuildConfig: &bean2.CiBuildConfigBean{
+				Id:                20,
+				GitMaterialId:     22,
+				CiBuildType:       "self-dockerfile-build",
+				DockerBuildConfig: &bean2.DockerBuildConfig{DockerfileContent: ""},
+				BuildPackConfig:   nil,
+			},
+			UserId: 0,
+		}
+
+		MockedCiPipelineMaterialJson, _ := json.Marshal(PipelineMaterialsObject)
+
+		MockedCiTemplateOverrideHistory, _ := json.Marshal(
+			repository.CiPipelineTemplateOverrideHistoryDTO{
+				DockerRegistryId:      "prakash",
+				DockerRepository:      "prakash1001/sams-repository-3",
+				DockerfilePath:        "",
+				Active:                true,
+				CiBuildConfigId:       20,
+				BuildMetaDataType:     "self-dockerfile-build",
+				BuildMetadata:         "{\"dockerfileContent\":\"\"}",
+				IsCiTemplateOverriden: true,
+				AuditLog:              sql.AuditLog{},
+			},
+		)
+
+		mockedCiPipelineHistoryObject := repository.CiPipelineHistory{
+			Id:                        0,
+			CiPipelineId:              5,
+			CiTemplateOverrideHistory: string(MockedCiTemplateOverrideHistory),
+			CiPipelineMaterialHistory: string(MockedCiPipelineMaterialJson),
+			Trigger:                   "update",
+			ScanEnabled:               false,
+			Manual:                    false,
+		}
+
+		mockedCiPipelineHistoryRepository.On("Save", &mockedCiPipelineHistoryObject).Return(nil)
+
+		err = CiPipelineHistoryServiceImpl.SaveHistory(&PipelineObject, PipelineMaterialsObject, &CiTemplateObject, "update")
+
+		assert.Nil(t, err)
+
+	})
+
+}
diff --git a/pkg/pipeline/history/GitMaterialHistoryService_test.go b/pkg/pipeline/history/GitMaterialHistoryService_test.go
new file mode 100644
index 000000000..d0e1fd247
--- /dev/null
+++ b/pkg/pipeline/history/GitMaterialHistoryService_test.go
@@ -0,0 +1,93 @@
+package history
+
+import (
+	"github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig"
+	"github.com/devtron-labs/devtron/internal/util"
+	"github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
+	"github.com/devtron-labs/devtron/pkg/pipeline/history/repository/mocks"
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestGitMaterialService(t *testing.T) {
+
+	t.Run("Save", func(t *testing.T) {
+
+		sugaredLogger, err := util.NewSugardLogger()
+		assert.Nil(t, err)
+
+		mockedGitMaterialHistoryRepository := mocks.NewGitMaterialHistoryRepository(t)
+
+		GitHistoryServiceImpl := NewGitMaterialHistoryServiceImpl(mockedGitMaterialHistoryRepository, sugaredLogger)
+
+		GitMaterial := &pipelineConfig.GitMaterial{
+			Id:              1,
+			Url:             "https://github.com/devtron-labs/ci-runner",
+			AppId:           49,
+			Name:            "1-devtron-test",
+			Active:          true,
+			CheckoutPath:    "./",
+			FetchSubmodules: false,
+			AuditLog:        sql.AuditLog{},
+		}
+
+		mockedMaterial := &repository.GitMaterialHistory{
+			GitMaterialId:   1,
+			Url:             "https://github.com/devtron-labs/ci-runner",
+			AppId:           49,
+			Name:            "1-devtron-test",
+			Active:          true,
+			CheckoutPath:    "./",
+			FetchSubmodules: false,
+			AuditLog:        sql.AuditLog{},
+		}
+
+		mockedGitMaterialHistoryRepository.On("SaveGitMaterialHistory", mockedMaterial).Return(nil)
+
+		err = GitHistoryServiceImpl.CreateMaterialHistory(GitMaterial)
+
+		assert.Nil(t, err)
+
+	})
+
+	t.Run("MarkMaterialDelete", func(t *testing.T) {
+
+		sugaredLogger, err := util.NewSugardLogger()
+		assert.Nil(t, err)
+
+		mockedGitMaterialHistoryRepository := mocks.NewGitMaterialHistoryRepository(t)
+
+		GitHistoryServiceImpl := NewGitMaterialHistoryServiceImpl(mockedGitMaterialHistoryRepository, sugaredLogger)
+
+		mockedMaterial := &repository.GitMaterialHistory{
+			GitMaterialId:   1,
+			Url:             "https://github.com/devtron-labs/ci-runner",
+			AppId:           49,
+			Name:            "1-devtron-test",
+			Active:          false,
+			CheckoutPath:    "./",
+			FetchSubmodules: false,
+			AuditLog:        sql.AuditLog{},
+		}
+
+		GitMaterial := &pipelineConfig.GitMaterial{
+			Id:              1,
+			Url:             "https://github.com/devtron-labs/ci-runner",
+			AppId:           49,
+			Name:            "1-devtron-test",
+			Active:          true,
+			CheckoutPath:    "./",
+			FetchSubmodules: false,
+			AuditLog:        sql.AuditLog{},
+		}
+
+		mockedGitMaterialHistoryRepository.On("SaveGitMaterialHistory", mockedMaterial).Return(nil)
+
+		err = GitHistoryServiceImpl.MarkMaterialDeletedAndCreateHistory(GitMaterial)
+
+		assert.Nil(t, err)
+
+	})
+
+}
diff --git a/pkg/pipeline/history/ciTemplateHistoryService_test.go b/pkg/pipeline/history/ciTemplateHistoryService_test.go
new file mode 100644
index 000000000..138402ecd
--- /dev/null
+++ b/pkg/pipeline/history/ciTemplateHistoryService_test.go
@@ -0,0 +1,95 @@
+package history
+
+import (
+	"github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig"
+	"github.com/devtron-labs/devtron/internal/util"
+	bean2 "github.com/devtron-labs/devtron/pkg/pipeline/bean"
+	"github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
+	"github.com/devtron-labs/devtron/pkg/pipeline/history/repository/mocks"
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestCiTemplateHistoryService(t *testing.T) {
+
+	t.Run("SaveHistory", func(t *testing.T) {
+
+		sugaredLogger, err := util.NewSugardLogger()
+		assert.Nil(t, err)
+
+		mockedCiTemplateHistoryRepository := mocks.NewCiTemplateHistoryRepository(t)
+
+		CiTemplateHistoryServiceImpl := NewCiTemplateHistoryServiceImpl(mockedCiTemplateHistoryRepository, sugaredLogger)
+
+		mockedCiTemplateHistoryObject := repository.CiTemplateHistory{
+			CiTemplateId:       28,
+			AppId:              38,
+			DockerRegistryId:   "prakash",
+			DockerRepository:   "prakash1001/sams-repository-3",
+			DockerfilePath:     "",
+			Args:               "",
+			TargetPlatform:     "",
+			BeforeDockerBuild:  "",
+			AfterDockerBuild:   "",
+			TemplateName:       "",
+			Version:            "",
+			Active:             true,
+			GitMaterialId:      22,
+			DockerBuildOptions: "",
+			CiBuildConfigId:    20,
+			BuildMetaDataType:  "self-dockerfile-build",
+			BuildMetadata:      "{\"dockerfileContent\":\"\"}",
+			Trigger:            "update",
+			AuditLog:           sql.AuditLog{},
+		}
+		//dockerBuildOptions := map[string]string{}
+		//dockerBuildOptions["dockerfileRelativePath"] = "Dockerfile"
+		//dockerBuildOptions["dockerfileContent"] = ""
+		//dockerBuildOptions["dockerfileRelativePath"] = "Dockerfile"
+
+		ciServiceObject := bean2.CiTemplateBean{
+			CiTemplate: &pipelineConfig.CiTemplate{
+				Id:                 28,
+				AppId:              38,
+				DockerRegistryId:   "prakash",
+				DockerRepository:   "prakash1001/sams-repository-3",
+				DockerfilePath:     "",
+				Args:               "",
+				TargetPlatform:     "",
+				BeforeDockerBuild:  "",
+				AfterDockerBuild:   "",
+				TemplateName:       "",
+				Version:            "",
+				Active:             true,
+				GitMaterialId:      22,
+				DockerBuildOptions: "",
+				CiBuildConfigId:    20,
+				AuditLog:           sql.AuditLog{},
+				App:                nil,
+				DockerRegistry:     nil,
+				GitMaterial:        nil,
+				CiBuildConfig:      nil,
+			},
+			CiTemplateOverride: nil,
+			CiBuildConfig: &bean2.CiBuildConfigBean{
+				Id:                20,
+				GitMaterialId:     22,
+				CiBuildType:       "self-dockerfile-build",
+				DockerBuildConfig: &bean2.DockerBuildConfig{DockerfileContent: ""},
+				BuildPackConfig:   nil,
+			},
+			UserId: 0,
+		}
+
+		mockedCiTemplateHistoryRepository.On("Save", &mockedCiTemplateHistoryObject).Return(
+			nil,
+		)
+
+		err = CiTemplateHistoryServiceImpl.SaveHistory(&ciServiceObject, "update")
+
+		assert.Nil(t, err)
+
+	})
+
+}