Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/appbean/AppDetail.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type GitMaterial struct {
GitRepoUrl string `json:"gitRepoUrl,notnull" validate:"required"`
CheckoutPath string `json:"checkoutPath,notnull" validate:"required"`
FetchSubmodules bool `json:"fetchSubmodules"`
CloningMode string `json:"cloningMode" validate:"omitempty,oneof=FULL SHALLOW"`
}

type DockerConfig struct {
Expand Down
2 changes: 2 additions & 0 deletions api/restHandler/CoreAppRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ func (handler CoreAppRestHandlerImpl) buildAppGitMaterials(appId int) ([]*appBea
GitRepoUrl: gitMaterial.Url,
CheckoutPath: gitMaterial.CheckoutPath,
FetchSubmodules: gitMaterial.FetchSubmodules,
CloningMode: gitMaterial.CloningMode,
GitProviderUrl: gitRegistry.Url,
})
}
Expand Down Expand Up @@ -1312,6 +1313,7 @@ func (handler CoreAppRestHandlerImpl) createGitMaterials(appId int, gitMaterials
GitProviderId: gitProvider.Id,
CheckoutPath: material.CheckoutPath,
FetchSubmodules: material.FetchSubmodules,
CloningMode: material.CloningMode,
}

createMaterialRequest.Material = append(createMaterialRequest.Material, gitMaterialRequest)
Expand Down
14 changes: 8 additions & 6 deletions pkg/appClone/AppCloneService.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,14 @@ func (impl *AppCloneServiceImpl) CloneGitRepo(oldAppId, newAppId int, userId int
gitMaterialsMap := make(map[int]int)
for _, material := range originalApp.Material {
gitMaterial := &bean.GitMaterial{
Name: material.Name,
Url: material.Url,
Id: 0,
GitProviderId: material.GitProviderId,
CheckoutPath: material.CheckoutPath,
FilterPattern: material.FilterPattern,
Name: material.Name,
Url: material.Url,
Id: 0,
GitProviderId: material.GitProviderId,
CheckoutPath: material.CheckoutPath,
FetchSubmodules: material.FetchSubmodules,
CloningMode: material.CloningMode,
FilterPattern: material.FilterPattern,
}
createMaterial.Material = []*bean.GitMaterial{gitMaterial} // append(createMaterial.Material, gitMaterial)
createMaterialres, err := impl.pipelineBuilder.CreateMaterialsForApp(createMaterial)
Expand Down
12 changes: 12 additions & 0 deletions pkg/bean/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,23 @@ type GitMaterial struct {
GitProviderId int `json:"gitProviderId,omitempty" validate:"gt=0"`
CheckoutPath string `json:"checkoutPath" validate:"checkout-path-component"`
FetchSubmodules bool `json:"fetchSubmodules"`
CloningMode string `json:"cloningMode" validate:"omitempty,oneof=FULL SHALLOW"`
IsUsedInCiConfig bool `json:"isUsedInCiConfig"`
FilterPattern []string `json:"filterPattern"`
CreateBackup bool `json:"createBackup"`
}

const (
GitMaterialCloningModeFull = "FULL"
GitMaterialCloningModeShallow = "SHALLOW"
)

func (m *GitMaterial) SetDefaultCloningMode() {
if m.CloningMode == "" {
m.CloningMode = GitMaterialCloningModeFull
}
}

// UpdateSanitisedGitRepoUrl will remove all trailing slashes , leading and trailing spaces from git repository url
func (m *GitMaterial) UpdateSanitisedGitRepoUrl() {
for strings.HasSuffix(m.Url, "/") {
Expand Down
41 changes: 41 additions & 0 deletions pkg/bean/git_material_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2026. Devtron Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package bean

import "testing"

func TestGitMaterialSetDefaultCloningMode(t *testing.T) {
t.Run("defaults an omitted mode to full", func(t *testing.T) {
material := &GitMaterial{}

material.SetDefaultCloningMode()

if material.CloningMode != GitMaterialCloningModeFull {
t.Fatalf("expected %q, got %q", GitMaterialCloningModeFull, material.CloningMode)
}
})

t.Run("preserves an explicitly selected mode", func(t *testing.T) {
material := &GitMaterial{CloningMode: GitMaterialCloningModeShallow}

material.SetDefaultCloningMode()

if material.CloningMode != GitMaterialCloningModeShallow {
t.Fatalf("expected %q, got %q", GitMaterialCloningModeShallow, material.CloningMode)
}
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type GitMaterial struct {
Name string `sql:"name, omitempty"`
CheckoutPath string `sql:"checkout_path, omitempty"`
FetchSubmodules bool `sql:"fetch_submodules,notnull"`
CloningMode string `sql:"cloning_mode,notnull"`
FilterPattern []string `sql:"filter_pattern"`
sql.AuditLog
App *app.App
Expand Down
1 change: 1 addition & 0 deletions pkg/build/trigger/HandlerService.go
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,7 @@ func (impl *HandlerServiceImpl) buildWfRequestForCiPipeline(pipeline *pipelineCo
MaterialName: ciMaterial.GitMaterial.Name,
CheckoutPath: ciMaterial.GitMaterial.CheckoutPath,
FetchSubmodules: ciMaterial.GitMaterial.FetchSubmodules,
CloningMode: ciMaterial.GitMaterial.CloningMode,
CommitHash: commitHashForPipelineId.Commit,
Author: commitHashForPipelineId.Author,
SourceType: ciMaterial.Type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ func (impl *HandlerServiceImpl) buildWFRequest(runner *pipelineConfig.CdWorkflow
MaterialName: gitMaterial.Name,
CheckoutPath: gitMaterial.CheckoutPath,
FetchSubmodules: gitMaterial.FetchSubmodules,
CloningMode: gitMaterial.CloningMode,
SourceType: m.Type,
SourceValue: m.Value,
Type: string(m.Type),
Expand Down
110 changes: 91 additions & 19 deletions pkg/pipeline/CiCdPipelineOrchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ type CiCdPipelineOrchestrator interface {
GetWorkflowCacheConfig(appType helper.AppType, pipelineType string, pipelineWorkflowCacheConfig common2.WorkflowCacheConfigType) bean.WorkflowCacheConfig
}

const gitMaterialCreationAdvisoryLockNamespace = 6344

type CiCdPipelineOrchestratorImpl struct {
appRepository app2.AppRepository
logger *zap.SugaredLogger
Expand Down Expand Up @@ -1365,7 +1367,7 @@ func (impl CiCdPipelineOrchestratorImpl) DeleteApp(appId int, userId int32) erro

impl.logger.Debug("deleting materials in git_sensor")
for _, m := range materials {
err = impl.updateRepositoryToGitSensor(m, "", false)
err = impl.updateRepositoryToGitSensor(m, false)
if err != nil {
impl.logger.Errorw("error in updating to git-sensor", "err", err)
return err
Expand Down Expand Up @@ -1426,35 +1428,60 @@ func (impl CiCdPipelineOrchestratorImpl) CreateMaterials(createMaterialRequest *
return nil, err
}
defer tx.Rollback()
// Material creation commits before git-sensor imports the repository. Serializing requests per app makes
// retries safe while a large repository is still being imported by the original request.
_, err = tx.Exec("SELECT pg_advisory_xact_lock(?, ?)", gitMaterialCreationAdvisoryLockNamespace, createMaterialRequest.AppId)
if err != nil {
impl.logger.Errorw("error acquiring git material creation lock", "appId", createMaterialRequest.AppId, "err", err)
return nil, err
}
existingMaterials, err := impl.materialRepository.FindByAppId(createMaterialRequest.AppId)
if err != nil {
impl.logger.Errorw("err", "err", err)
return nil, err
}
checkoutPaths := make(map[int]string)
existingMaterialByIdentity := make(map[string]*repository6.GitMaterial, len(existingMaterials))
impl.logger.Debugw("existing materials", "material", existingMaterials)
for _, material := range existingMaterials {
checkoutPaths[material.Id] = material.CheckoutPath
existingMaterialByIdentity[gitMaterialIdentity(material.Url, material.GitProviderId, material.CheckoutPath)] = material
}
var materialsToCreate []*bean.GitMaterial
for i, material := range createMaterialRequest.Material {
if material.CheckoutPath == "" {
material.CheckoutPath = "./"
}
checkoutPaths[i*-1] = material.CheckoutPath
material.UpdateSanitisedGitRepoUrl()
material.SetDefaultCloningMode()
if err = validateGitMaterialCloningMode(material.CloningMode); err != nil {
return nil, err
}
existingMaterial := existingMaterialByIdentity[gitMaterialIdentity(material.Url, material.GitProviderId, material.CheckoutPath)]
if existingMaterial != nil {
if !gitMaterialOptionsMatch(existingMaterial, material) {
return nil, fmt.Errorf("git material for checkout path %q already exists; use update material to change its options", material.CheckoutPath)
}
material.Id = existingMaterial.Id
material.Name = existingMaterial.Name
continue
}
checkoutPaths[(i+1)*-1] = material.CheckoutPath
materialsToCreate = append(materialsToCreate, material)
}
duplicatePathErr := impl.validateCheckoutPathsForMultiGit(checkoutPaths)
if duplicatePathErr != nil {
impl.logger.Errorw("duplicate checkout paths", "err", err)
impl.logger.Errorw("duplicate checkout paths", "err", duplicatePathErr)
return nil, duplicatePathErr
}
var materials []*bean.GitMaterial
for _, inputMaterial := range createMaterialRequest.Material {
inputMaterial.UpdateSanitisedGitRepoUrl()
for _, inputMaterial := range materialsToCreate {
m, err := impl.createMaterial(tx, inputMaterial, createMaterialRequest.AppId, createMaterialRequest.UserId)
inputMaterial.Id = m.Id
if err != nil {
return nil, err
}
inputMaterial.Id = m.Id
inputMaterial.Name = m.Name
materials = append(materials, inputMaterial)
}
// moving transaction before addRepositoryToGitSensor as commiting transaction after addRepositoryToGitSensor was causing problems
Expand All @@ -1464,7 +1491,7 @@ func (impl CiCdPipelineOrchestratorImpl) CreateMaterials(createMaterialRequest *
impl.logger.Errorw("error in committing tx Create material", "err", err, "materials", materials)
return nil, err
}
err = impl.addRepositoryToGitSensor(materials, "")
err = impl.addRepositoryToGitSensor(materials)
if err != nil {
impl.logger.Errorw("error in updating to sensor", "err", err)
return nil, err
Expand All @@ -1490,17 +1517,15 @@ func (impl CiCdPipelineOrchestratorImpl) UpdateMaterial(updateMaterialDTO *bean.
return nil, err
}

err = impl.updateRepositoryToGitSensor(updatedMaterial, "",
updateMaterialDTO.Material.CreateBackup)
err = impl.updateRepositoryToGitSensor(updatedMaterial, updateMaterialDTO.Material.CreateBackup)
if err != nil {
impl.logger.Errorw("error in updating to git-sensor", "err", err)
return nil, err
}
return updateMaterialDTO, nil
}

func (impl CiCdPipelineOrchestratorImpl) updateRepositoryToGitSensor(material *repository6.GitMaterial,
cloningMode string, createBackup bool) error {
func (impl CiCdPipelineOrchestratorImpl) updateRepositoryToGitSensor(material *repository6.GitMaterial, createBackup bool) error {
sensorMaterial := &gitSensor.GitMaterial{
Name: material.Name,
Url: material.Url,
Expand All @@ -1510,7 +1535,7 @@ func (impl CiCdPipelineOrchestratorImpl) updateRepositoryToGitSensor(material *r
Deleted: !material.Active,
FetchSubmodules: material.FetchSubmodules,
FilterPattern: material.FilterPattern,
CloningMode: cloningMode,
CloningMode: material.CloningMode,
CreateBackup: createBackup,
}
timeout := 10 * time.Minute
Expand All @@ -1523,8 +1548,8 @@ func (impl CiCdPipelineOrchestratorImpl) updateRepositoryToGitSensor(material *r
return impl.GitSensorClient.UpdateRepo(ctx, sensorMaterial)
}

func (impl CiCdPipelineOrchestratorImpl) addRepositoryToGitSensor(materials []*bean.GitMaterial, cloningMode string) error {
var sensorMaterials []*gitSensor.GitMaterial
func (impl CiCdPipelineOrchestratorImpl) addRepositoryToGitSensor(materials []*bean.GitMaterial) error {
sensorMaterialsByCloningMode := make(map[string][]*gitSensor.GitMaterial)
for _, material := range materials {
sensorMaterial := &gitSensor.GitMaterial{
Name: material.Name,
Expand All @@ -1534,13 +1559,25 @@ func (impl CiCdPipelineOrchestratorImpl) addRepositoryToGitSensor(materials []*b
Deleted: false,
FetchSubmodules: material.FetchSubmodules,
FilterPattern: material.FilterPattern,
CloningMode: cloningMode,
CloningMode: material.CloningMode,
}
sensorMaterials = append(sensorMaterials, sensorMaterial)
sensorMaterialsByCloningMode[material.CloningMode] = append(sensorMaterialsByCloningMode[material.CloningMode], sensorMaterial)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
return impl.GitSensorClient.AddRepo(ctx, sensorMaterials)
// git-sensor currently applies the first material's cloning mode to an AddRepo batch. Keep batches homogeneous
// so applications with multiple sources can configure FULL and SHALLOW independently.
for _, cloningMode := range []string{bean.GitMaterialCloningModeFull, bean.GitMaterialCloningModeShallow} {
sensorMaterials := sensorMaterialsByCloningMode[cloningMode]
if len(sensorMaterials) == 0 {
continue
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
err := impl.GitSensorClient.AddRepo(ctx, sensorMaterials)
cancel()
if err != nil {
return err
}
}
return nil
}

// FIXME: not thread safe
Expand Down Expand Up @@ -1642,6 +1679,32 @@ func (impl CiCdPipelineOrchestratorImpl) validateCheckoutPathsForMultiGit(allPat
return nil
}

func gitMaterialIdentity(url string, gitProviderId int, checkoutPath string) string {
url = strings.TrimRight(strings.TrimSpace(url), "/")
if checkoutPath == "" {
checkoutPath = "./"
}
return fmt.Sprintf("%d\x00%s\x00%s", gitProviderId, url, checkoutPath)
}

func gitMaterialOptionsMatch(existingMaterial *repository6.GitMaterial, requestedMaterial *bean.GitMaterial) bool {
existingCloningMode := existingMaterial.CloningMode
if existingCloningMode == "" {
existingCloningMode = bean.GitMaterialCloningModeFull
}
return existingMaterial.FetchSubmodules == requestedMaterial.FetchSubmodules &&
existingCloningMode == requestedMaterial.CloningMode &&
slices.Equal(existingMaterial.FilterPattern, requestedMaterial.FilterPattern)
}

func validateGitMaterialCloningMode(cloningMode string) error {
if cloningMode != bean.GitMaterialCloningModeFull && cloningMode != bean.GitMaterialCloningModeShallow {
return fmt.Errorf("unsupported git material cloning mode %q; supported values are %s and %s",
cloningMode, bean.GitMaterialCloningModeFull, bean.GitMaterialCloningModeShallow)
}
return nil
}

func (impl CiCdPipelineOrchestratorImpl) updateMaterial(tx *pg.Tx, updateMaterialDTO *bean.UpdateMaterialDTO) (*repository6.GitMaterial, error) {
existingMaterials, err := impl.materialRepository.FindByAppId(updateMaterialDTO.AppId)
if err != nil {
Expand All @@ -1662,6 +1725,13 @@ func (impl CiCdPipelineOrchestratorImpl) updateMaterial(tx *pg.Tx, updateMateria
if currentMaterial == nil {
return nil, errors.New("material to be updated does not exist")
}
if updateMaterialDTO.Material.CloningMode == "" {
updateMaterialDTO.Material.CloningMode = currentMaterial.CloningMode
updateMaterialDTO.Material.SetDefaultCloningMode()
}
if err = validateGitMaterialCloningMode(updateMaterialDTO.Material.CloningMode); err != nil {
return nil, err
}
if updateMaterialDTO.Material.CheckoutPath == "" {
updateMaterialDTO.Material.CheckoutPath = "./"
}
Expand All @@ -1680,6 +1750,7 @@ func (impl CiCdPipelineOrchestratorImpl) updateMaterial(tx *pg.Tx, updateMateria
currentMaterial.GitProviderId = updateMaterialDTO.Material.GitProviderId
currentMaterial.CheckoutPath = updateMaterialDTO.Material.CheckoutPath
currentMaterial.FetchSubmodules = updateMaterialDTO.Material.FetchSubmodules
currentMaterial.CloningMode = updateMaterialDTO.Material.CloningMode
currentMaterial.FilterPattern = updateMaterialDTO.Material.FilterPattern
currentMaterial.AuditLog = sql.AuditLog{UpdatedBy: updateMaterialDTO.UserId, CreatedBy: currentMaterial.CreatedBy, UpdatedOn: time.Now(), CreatedOn: currentMaterial.CreatedOn}

Expand All @@ -1706,6 +1777,7 @@ func (impl CiCdPipelineOrchestratorImpl) createMaterial(tx *pg.Tx, inputMaterial
Active: true,
CheckoutPath: inputMaterial.CheckoutPath,
FetchSubmodules: inputMaterial.FetchSubmodules,
CloningMode: inputMaterial.CloningMode,
FilterPattern: inputMaterial.FilterPattern,
AuditLog: sql.AuditLog{UpdatedBy: userId, CreatedBy: userId, UpdatedOn: time.Now(), CreatedOn: time.Now()},
}
Expand Down
1 change: 1 addition & 0 deletions pkg/pipeline/CiMaterialConfigService.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ func (impl *CiMaterialConfigServiceImpl) GetMaterialsForAppId(appId int) []*bean
GitProviderId: material.GitProviderId,
CheckoutPath: material.CheckoutPath,
FetchSubmodules: material.FetchSubmodules,
CloningMode: material.CloningMode,
FilterPattern: material.FilterPattern,
}
//check if git material is deletable or not
Expand Down
13 changes: 8 additions & 5 deletions pkg/pipeline/PipelineBuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,14 @@ func (impl *PipelineBuilderImpl) getGitMaterialsForApp(appId int) ([]*bean.GitMa
gitUrl = u.String()
}
gitMaterial := &bean.GitMaterial{
Id: material.Id,
Url: gitUrl,
GitProviderId: material.GitProviderId,
Name: material.Name[strings.Index(material.Name, "-")+1:],
CheckoutPath: material.CheckoutPath,
Id: material.Id,
Url: gitUrl,
GitProviderId: material.GitProviderId,
Name: material.Name[strings.Index(material.Name, "-")+1:],
CheckoutPath: material.CheckoutPath,
FetchSubmodules: material.FetchSubmodules,
CloningMode: material.CloningMode,
FilterPattern: material.FilterPattern,
}
gitMaterials = append(gitMaterials, gitMaterial)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/pipeline/bean/workFlowRequestBean.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type CiProjectDetails struct {
MaterialName string `json:"materialName"`
CheckoutPath string `json:"checkoutPath"`
FetchSubmodules bool `json:"fetchSubmodules"`
CloningMode string `json:"cloningMode"`
CommitHash string `json:"commitHash"`
GitTag string `json:"gitTag"`
CommitTime string `json:"commitTime"`
Expand Down
Loading