Skip to content

Commit 30b5949

Browse files
authored
Align API types between Porch and kpt (#4341)
* Sync kpt type definitions across kpt and porch Signed-off-by: liamfallon <liam.fallon@est.tech> * Add copy-merge strategy to kpt Signed-off-by: liamfallon <liam.fallon@est.tech> * Fix copyrights Signed-off-by: liamfallon <liam.fallon@est.tech> * Fix lint errors Signed-off-by: liamfallon <liam.fallon@est.tech> * Use template YEAR in boilerplate Signed-off-by: liamfallon <liam.fallon@est.tech> --------- Signed-off-by: liamfallon <liam.fallon@est.tech>
1 parent 65a0e27 commit 30b5949

File tree

11 files changed

+625
-26
lines changed

11 files changed

+625
-26
lines changed

internal/util/pkgutil/pkgutil.go

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020 The kpt Authors
1+
// Copyright 2020,2026 The kpt Authors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
// Package pkgutil contains utility functions for packages
1516
package pkgutil
1617

1718
import (
@@ -169,6 +170,62 @@ func CopyPackage(src, dst string, copyRootKptfile bool, matcher pkg.SubpackageMa
169170
return nil
170171
}
171172

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

internal/util/pkgutil/pkgutil_test.go

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

internal/util/update/copy-merge.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2026 The kpt Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package update
16+
17+
import (
18+
"github.com/kptdev/kpt/internal/errors"
19+
"github.com/kptdev/kpt/internal/pkg"
20+
"github.com/kptdev/kpt/internal/util/pkgutil"
21+
"github.com/kptdev/kpt/pkg/kptfile/kptfileutil"
22+
"github.com/kptdev/kpt/pkg/lib/types"
23+
)
24+
25+
// CopyMergeUpdater is responsible for synchronizing the destination package
26+
// with the source package by updating the Kptfile and copying and replacing package contents.
27+
type CopyMergeUpdater struct{}
28+
29+
// Update synchronizes the destination/local package with the source/update package by updating the Kptfile
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
33+
// and other parameters for the update operation. Returns an error if the update fails.
34+
func (u CopyMergeUpdater) Update(options Options) error {
35+
const op errors.Op = "update.Update"
36+
37+
dst := options.LocalPath
38+
src := options.UpdatedPath
39+
org := options.OriginPath
40+
41+
if err := kptfileutil.UpdateKptfile(dst, src, options.OriginPath, true); err != nil {
42+
return errors.E(op, types.UniquePath(dst), err)
43+
}
44+
if err := pkgutil.CopyPackage(src, dst, options.IsRoot, pkg.All); err != nil {
45+
return errors.E(op, types.UniquePath(dst), err)
46+
}
47+
if err := pkgutil.RemoveStaleItems(org, src, dst, options.IsRoot, pkg.All); err != nil {
48+
return errors.E(op, types.UniquePath(dst), err)
49+
}
50+
return nil
51+
}

0 commit comments

Comments
 (0)