Skip to content

Commit ca8445a

Browse files
lapentadkushnaidu
andauthored
Added RemoveStaleItems in copy-merge (#306)
* added RemoveStaleItems in copy-merge Signed-off-by: lapentafd <francesco.lapenta@est.tech> * adding unit tests Signed-off-by: lapentafd <francesco.lapenta@est.tech> --------- Signed-off-by: lapentafd <francesco.lapenta@est.tech> Co-authored-by: Kushal Harish Naidu <kushal.harish.naidu@ericsson.com>
1 parent b63424e commit ca8445a

4 files changed

Lines changed: 135 additions & 4 deletions

File tree

internal/kpt/util/pkgutil/pkgutil.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,62 @@ func CopyPackage(src, dst string, copyRootKptfile bool, matcher pkg.SubpackageMa
169169
return nil
170170
}
171171

172+
// RemoveStaleItems removes files and directories from the dst package that were present in the org package,
173+
// but are not present in the src package. It does not remove the root Kptfile of the dst package.
174+
func RemoveStaleItems(org, src, dst string, copyRootKptfile bool, matcher pkg.SubpackageMatcher) error {
175+
var dirsToDelete []string
176+
walkErr := filepath.Walk(dst, func(path string, info os.FileInfo, err error) error {
177+
if err != nil {
178+
return err
179+
}
180+
// The root directory should never be deleted.
181+
if path == dst {
182+
return nil
183+
}
184+
185+
relPath, err := filepath.Rel(dst, path)
186+
if err != nil {
187+
return err
188+
}
189+
190+
// Skip the root Kptfile
191+
if relPath == kptfilev1.KptFileName {
192+
return nil
193+
}
194+
195+
srcPath := filepath.Join(src, relPath)
196+
orgPath := filepath.Join(org, relPath)
197+
198+
_, srcErr := os.Stat(srcPath)
199+
_, orgErr := os.Stat(orgPath)
200+
201+
// Only remove if:
202+
// - not present in src (srcErr is os.IsNotExist)
203+
// - present in org (orgErr is nil)
204+
if os.IsNotExist(srcErr) && orgErr == nil {
205+
if info.IsDir() {
206+
dirsToDelete = append(dirsToDelete, path)
207+
} else {
208+
if err := os.Remove(path); err != nil {
209+
return err
210+
}
211+
}
212+
}
213+
return nil
214+
})
215+
if walkErr != nil {
216+
return walkErr
217+
}
218+
sort.Slice(dirsToDelete, SubPkgFirstSorter(dirsToDelete))
219+
for _, dir := range dirsToDelete {
220+
if err := os.Remove(dir); err != nil {
221+
return err
222+
}
223+
}
224+
225+
return nil
226+
}
227+
172228
func RemovePackageContent(path string, removeRootKptfile bool) error {
173229
// Walk the package (while ignoring subpackages) and delete all files.
174230
// We capture the paths to any subdirectories in the package so we

internal/kpt/util/pkgutil/pkgutil_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,3 +549,43 @@ func TestFindLocalRecursiveSubpackagesForPaths(t *testing.T) {
549549
})
550550
}
551551
}
552+
553+
554+
func TestRemoveStaleItems_RemovesFile(t *testing.T) {
555+
org := t.TempDir()
556+
src := t.TempDir()
557+
dst := t.TempDir()
558+
559+
// Create a file in org and dst, but not in src
560+
fileName := "file.txt"
561+
assert.NoError(t, os.WriteFile(filepath.Join(org, fileName), []byte("content"), 0644))
562+
assert.NoError(t, os.WriteFile(filepath.Join(dst, fileName), []byte("content"), 0644))
563+
564+
// Should remove file.txt from dst
565+
err := pkgutil.RemoveStaleItems(org, src, dst, true, pkg.All)
566+
assert.NoError(t, err)
567+
_, err = os.Stat(filepath.Join(dst, fileName))
568+
assert.True(t, os.IsNotExist(err))
569+
}
570+
571+
func TestRemoveStaleItems_ErrorOnRemove(t *testing.T) {
572+
org := t.TempDir()
573+
src := t.TempDir()
574+
dst := t.TempDir()
575+
576+
fileName := "file.txt"
577+
filePathDst := filepath.Join(dst, fileName)
578+
filePathOrg := filepath.Join(org, fileName)
579+
580+
assert.NoError(t, os.WriteFile(filePathOrg, []byte("content"), 0644))
581+
assert.NoError(t, os.WriteFile(filePathDst, []byte("content"), 0644))
582+
583+
// Replace file in dst with a non-empty directory to force os.Remove error
584+
assert.NoError(t, os.Remove(filePathDst))
585+
assert.NoError(t, os.Mkdir(filePathDst, 0755))
586+
assert.NoError(t, os.WriteFile(filepath.Join(filePathDst, "dummy"), []byte("x"), 0644))
587+
588+
err := pkgutil.RemoveStaleItems(org, src, dst, true, pkg.All)
589+
assert.Error(t, err)
590+
assert.Contains(t, err.Error(), "directory not empty")
591+
}

internal/kpt/util/update/copy-merge.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,25 @@ import (
2727
type CopyMergeUpdater struct{}
2828

2929
// Update synchronizes the destination/local package with the source/update package by updating the Kptfile
30-
// and copying package contents. It takes an Options struct as input, which specifies the paths
30+
// and copying package contents. It deletes resources from the destination package if they were present
31+
// in the original package, but not present anymore in the source package.
32+
// It takes an Options struct as input, which specifies the paths
3133
// and other parameters for the update operation. Returns an error if the update fails.
3234
func (u CopyMergeUpdater) Update(options Options) error {
3335
const op errors.Op = "update.Update"
3436

3537
dst := options.LocalPath
3638
src := options.UpdatedPath
39+
org := options.OriginPath
3740

3841
if err := kptfileutil.UpdateKptfile(dst, src, options.OriginPath, true); err != nil {
3942
return errors.E(op, types.UniquePath(dst), err)
4043
}
4144
if err := pkgutil.CopyPackage(src, dst, options.IsRoot, pkg.All); err != nil {
4245
return errors.E(op, types.UniquePath(dst), err)
4346
}
47+
if err := pkgutil.RemoveStaleItems(org, src, dst, options.IsRoot, pkg.All); err != nil {
48+
return errors.E(op, types.UniquePath(dst), err)
49+
}
4450
return nil
4551
}

internal/kpt/util/update/copy-merge_test.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func TestCopyMerge(t *testing.T) {
181181
WithResource(pkgbuilder.DeploymentResource),
182182
),
183183
},
184-
"update existing file in origin, local, and updated": {
184+
"file removal if file exists in origin but not in update": {
185185
origin: pkgbuilder.NewRootPkg().
186186
WithKptfile(
187187
pkgbuilder.NewKptfile().
@@ -207,8 +207,7 @@ func TestCopyMerge(t *testing.T) {
207207
pkgbuilder.NewKptfile().
208208
WithUpstream(kptRepo, "/origin", "master", copyMergeLiteral).
209209
WithUpstreamLock(kptRepo, "/origin", "master", "abc123"),
210-
).
211-
WithResource(pkgbuilder.DeploymentResource),
210+
),
212211
},
213212
}
214213

@@ -415,3 +414,33 @@ func TestCopyMergeDifferentMetadata(t *testing.T) {
415414
})
416415
}
417416
}
417+
418+
func TestCopyMergeErrorRemovingFile(t *testing.T) {
419+
src := t.TempDir()
420+
dst := t.TempDir()
421+
org := t.TempDir()
422+
423+
// Create a file in org and dst, but not in src (so RemoveStaleItems will try to remove it)
424+
fileName := "file.txt"
425+
filePathDst := filepath.Join(dst, fileName)
426+
filePathOrg := filepath.Join(org, fileName)
427+
428+
assert.NoError(t, os.WriteFile(filePathDst, []byte("content"), 0644))
429+
assert.NoError(t, os.WriteFile(filePathOrg, []byte("content"), 0644))
430+
431+
assert.NoError(t, os.Remove(filePathDst))
432+
assert.NoError(t, os.Mkdir(filePathDst, 0755))
433+
assert.NoError(t, os.WriteFile(filepath.Join(filePathDst, "dummy"), []byte("x"), 0644))
434+
435+
updater := &CopyMergeUpdater{}
436+
options := Options{
437+
OriginPath: org,
438+
UpdatedPath: src,
439+
LocalPath: dst,
440+
IsRoot: true,
441+
}
442+
443+
err := updater.Update(options)
444+
assert.Error(t, err)
445+
assert.Contains(t, err.Error(), "directory not empty")
446+
}

0 commit comments

Comments
 (0)