diff --git a/internal/util/pkgutil/pkgutil.go b/internal/util/pkgutil/pkgutil.go index a072c4fc7b..5daedf9ab9 100644 --- a/internal/util/pkgutil/pkgutil.go +++ b/internal/util/pkgutil/pkgutil.go @@ -1,4 +1,4 @@ -// Copyright 2020 The kpt Authors +// Copyright 2020,2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package pkgutil contains utility functions for packages package pkgutil import ( @@ -169,6 +170,62 @@ func CopyPackage(src, dst string, copyRootKptfile bool, matcher pkg.SubpackageMa return nil } +// RemoveStaleItems removes files and directories from the dst package that were present in the org package, +// but are not present in the src package. It does not remove the root Kptfile of the dst package. +func RemoveStaleItems(org, src, dst string, _ bool, _ pkg.SubpackageMatcher) error { + var dirsToDelete []string + walkErr := filepath.Walk(dst, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + // The root directory should never be deleted. + if path == dst { + return nil + } + + relPath, err := filepath.Rel(dst, path) + if err != nil { + return err + } + + // Skip the root Kptfile + if relPath == kptfilev1.KptFileName { + return nil + } + + srcPath := filepath.Join(src, relPath) + orgPath := filepath.Join(org, relPath) + + _, srcErr := os.Stat(srcPath) + _, orgErr := os.Stat(orgPath) + + // Only remove if: + // - not present in src (srcErr is os.IsNotExist) + // - present in org (orgErr is nil) + if os.IsNotExist(srcErr) && orgErr == nil { + if info.IsDir() { + dirsToDelete = append(dirsToDelete, path) + } else { + if err := os.Remove(path); err != nil { + return err + } + } + } + return nil + }) + if walkErr != nil { + return walkErr + } + sort.Slice(dirsToDelete, SubPkgFirstSorter(dirsToDelete)) + for _, dir := range dirsToDelete { + if err := os.Remove(dir); err != nil { + return err + } + } + + return nil +} + func RemovePackageContent(path string, removeRootKptfile bool) error { // Walk the package (while ignoring subpackages) and delete all files. // We capture the paths to any subdirectories in the package so we @@ -209,7 +266,6 @@ func RemovePackageContent(path string, removeRootKptfile bool) error { if err != nil { return err } - defer f.Close() // List up to one file or folder in the directory. _, err = f.Readdirnames(1) if err != nil && err != io.EOF { diff --git a/internal/util/pkgutil/pkgutil_test.go b/internal/util/pkgutil/pkgutil_test.go index c64b2ef815..06d1dbaff8 100644 --- a/internal/util/pkgutil/pkgutil_test.go +++ b/internal/util/pkgutil/pkgutil_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 The kpt Authors +// Copyright 2021,2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -549,3 +549,42 @@ func TestFindLocalRecursiveSubpackagesForPaths(t *testing.T) { }) } } + +func TestRemoveStaleItems_RemovesFile(t *testing.T) { + org := t.TempDir() + src := t.TempDir() + dst := t.TempDir() + + // Create a file in org and dst, but not in src + fileName := "file.txt" + assert.NoError(t, os.WriteFile(filepath.Join(org, fileName), []byte("content"), 0644)) + assert.NoError(t, os.WriteFile(filepath.Join(dst, fileName), []byte("content"), 0644)) + + // Should remove file.txt from dst + err := pkgutil.RemoveStaleItems(org, src, dst, true, pkg.All) + assert.NoError(t, err) + _, err = os.Stat(filepath.Join(dst, fileName)) + assert.True(t, os.IsNotExist(err)) +} + +func TestRemoveStaleItems_ErrorOnRemove(t *testing.T) { + org := t.TempDir() + src := t.TempDir() + dst := t.TempDir() + + fileName := "file.txt" + filePathDst := filepath.Join(dst, fileName) + filePathOrg := filepath.Join(org, fileName) + + assert.NoError(t, os.WriteFile(filePathOrg, []byte("content"), 0644)) + assert.NoError(t, os.WriteFile(filePathDst, []byte("content"), 0644)) + + // Replace file in dst with a non-empty directory to force os.Remove error + assert.NoError(t, os.Remove(filePathDst)) + assert.NoError(t, os.Mkdir(filePathDst, 0755)) + assert.NoError(t, os.WriteFile(filepath.Join(filePathDst, "dummy"), []byte("x"), 0644)) + + err := pkgutil.RemoveStaleItems(org, src, dst, true, pkg.All) + assert.Error(t, err) + assert.Contains(t, err.Error(), "directory not empty") +} diff --git a/internal/util/update/copy-merge.go b/internal/util/update/copy-merge.go new file mode 100644 index 0000000000..350ac04f96 --- /dev/null +++ b/internal/util/update/copy-merge.go @@ -0,0 +1,51 @@ +// Copyright 2026 The kpt Authors +// +// 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 update + +import ( + "github.com/kptdev/kpt/internal/errors" + "github.com/kptdev/kpt/internal/pkg" + "github.com/kptdev/kpt/internal/util/pkgutil" + "github.com/kptdev/kpt/pkg/kptfile/kptfileutil" + "github.com/kptdev/kpt/pkg/lib/types" +) + +// CopyMergeUpdater is responsible for synchronizing the destination package +// with the source package by updating the Kptfile and copying and replacing package contents. +type CopyMergeUpdater struct{} + +// Update synchronizes the destination/local package with the source/update package by updating the Kptfile +// and copying package contents. It deletes resources from the destination package if they were present +// in the original package, but not present anymore in the source package. +// It takes an Options struct as input, which specifies the paths +// and other parameters for the update operation. Returns an error if the update fails. +func (u CopyMergeUpdater) Update(options Options) error { + const op errors.Op = "update.Update" + + dst := options.LocalPath + src := options.UpdatedPath + org := options.OriginPath + + if err := kptfileutil.UpdateKptfile(dst, src, options.OriginPath, true); err != nil { + return errors.E(op, types.UniquePath(dst), err) + } + if err := pkgutil.CopyPackage(src, dst, options.IsRoot, pkg.All); err != nil { + return errors.E(op, types.UniquePath(dst), err) + } + if err := pkgutil.RemoveStaleItems(org, src, dst, options.IsRoot, pkg.All); err != nil { + return errors.E(op, types.UniquePath(dst), err) + } + return nil +} diff --git a/internal/util/update/copy-merge_test.go b/internal/util/update/copy-merge_test.go new file mode 100644 index 0000000000..203342f34e --- /dev/null +++ b/internal/util/update/copy-merge_test.go @@ -0,0 +1,441 @@ +// Copyright 2026 The kpt Authors +// +// 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 update_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/kptdev/kpt/internal/testutil" + "github.com/kptdev/kpt/internal/testutil/pkgbuilder" + "github.com/kptdev/kpt/internal/util/update" + "github.com/stretchr/testify/assert" +) + +const copyMergeLiteral = "copy-merge" + +func TestCopyMerge(t *testing.T) { + testCases := map[string]struct { + origin *pkgbuilder.RootPkg + local *pkgbuilder.RootPkg + updated *pkgbuilder.RootPkg + relPackagePath string + isRoot bool + expected *pkgbuilder.RootPkg + }{ + "only kpt file update": { + origin: pkgbuilder.NewRootPkg(), + local: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A0", "1", copyMergeLiteral), + ), + updated: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A0", "22", copyMergeLiteral), + ), + relPackagePath: "/", + isRoot: true, + expected: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A0", "22", copyMergeLiteral), + ), + }, + "new package and subpackage": { + origin: pkgbuilder.NewRootPkg(), + local: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A", "1", copyMergeLiteral), + ), + updated: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A", "22", copyMergeLiteral), + ). + WithResource(pkgbuilder.DeploymentResource). + WithSubPackages( + pkgbuilder.NewSubPkg("B"). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "b", "1", copyMergeLiteral), + ). + WithResource(pkgbuilder.DeploymentResource), + ), + relPackagePath: "/", + isRoot: true, + expected: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A", "22", copyMergeLiteral), + ). + WithResource(pkgbuilder.DeploymentResource). + WithSubPackages( + pkgbuilder.NewSubPkg("B"). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "b", "1", copyMergeLiteral), + ). + WithResource(pkgbuilder.DeploymentResource), + ), + }, + "adds and update package": { + origin: pkgbuilder.NewRootPkg(), + local: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A0", "1", copyMergeLiteral), + ). + WithResource(pkgbuilder.DeploymentResource). + WithSubPackages( + pkgbuilder.NewSubPkg("pkgA"). + WithResource(pkgbuilder.DeploymentResource), + pkgbuilder.NewSubPkg("pkgB"). + WithResource(pkgbuilder.DeploymentResource), + ), + updated: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A0", "1", copyMergeLiteral), + ). + WithResource(pkgbuilder.DeploymentResource). + WithSubPackages( + pkgbuilder.NewSubPkg("pkgA"). + WithResource(pkgbuilder.ConfigMapResource), + pkgbuilder.NewSubPkg("pkgC"). + WithResource(pkgbuilder.ConfigMapResource), + ), + relPackagePath: "/", + isRoot: true, + expected: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A0", "1", copyMergeLiteral), + ). + WithResource(pkgbuilder.DeploymentResource). + WithSubPackages( + pkgbuilder.NewSubPkg("pkgA"). + WithResource(pkgbuilder.ConfigMapResource). + WithResource(pkgbuilder.DeploymentResource), + pkgbuilder.NewSubPkg("pkgB"). + WithResource(pkgbuilder.DeploymentResource), + pkgbuilder.NewSubPkg("pkgC"). + WithResource(pkgbuilder.ConfigMapResource), + ), + }, + "updates local subpackages": { + origin: pkgbuilder.NewRootPkg(), + local: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "/", "master", copyMergeLiteral). + WithUpstreamLock(kptRepo, "/", "master", "A"), + ). + WithResource(pkgbuilder.DeploymentResource). + WithSubPackages( + pkgbuilder.NewSubPkg("foo"). + WithKptfile(). + WithResource(pkgbuilder.DeploymentResource), + ), + updated: pkgbuilder.NewRootPkg(). + WithKptfile(pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "/A", "newBranch", copyMergeLiteral). + WithUpstreamLock(kptRepo, "/A", "newBranch", "A"), + ). + WithResource(pkgbuilder.DeploymentResource). + WithSubPackages( + pkgbuilder.NewSubPkg("foo2"). + WithKptfile(). + WithResource(pkgbuilder.ConfigMapResource), + ), + relPackagePath: "/", + isRoot: true, + expected: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "/A", "newBranch", copyMergeLiteral). + WithUpstreamLock(kptRepo, "/A", "newBranch", "A"), + ). + WithResource(pkgbuilder.DeploymentResource). + WithSubPackages( + pkgbuilder.NewSubPkg("foo2"). + WithKptfile(). + WithResource(pkgbuilder.ConfigMapResource), + pkgbuilder.NewSubPkg("foo"). + WithKptfile(). + WithResource(pkgbuilder.DeploymentResource), + ), + }, + "file removal if file exists in origin but not in update": { + origin: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "/origin", "master", copyMergeLiteral), + ). + WithResource(pkgbuilder.DeploymentResource), + local: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "/origin", "master", copyMergeLiteral), + ). + WithResource(pkgbuilder.DeploymentResource), + updated: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "/origin", "master", copyMergeLiteral). + WithUpstreamLock(kptRepo, "/origin", "master", "abc123"), + ), + relPackagePath: "/", + isRoot: true, + expected: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "/origin", "master", copyMergeLiteral). + WithUpstreamLock(kptRepo, "/origin", "master", "abc123"), + ), + }, + } + + for tn, tc := range testCases { + t.Run(tn, func(t *testing.T) { + repos := testutil.EmptyReposInfo + origin := tc.origin.ExpandPkg(t, repos) + local := tc.local.ExpandPkg(t, repos) + updated := tc.updated.ExpandPkg(t, repos) + expected := tc.expected.ExpandPkg(t, repos) + + updater := &update.CopyMergeUpdater{} + + err := updater.Update(update.Options{ + RelPackagePath: tc.relPackagePath, + OriginPath: filepath.Join(origin, tc.relPackagePath), + LocalPath: filepath.Join(local, tc.relPackagePath), + UpdatedPath: filepath.Join(updated, tc.relPackagePath), + IsRoot: tc.isRoot, + }) + if !assert.NoError(t, err) { + t.FailNow() + } + + testutil.KptfileAwarePkgEqual(t, local, expected, false) + }) + } +} + +func TestCopyMergeError(t *testing.T) { + src := t.TempDir() + dst := t.TempDir() + + err := os.WriteFile(filepath.Join(src, "file.txt"), []byte("content"), 0644) + if err != nil { + t.Fatalf("Failed to write file: %v", err) + } + os.RemoveAll(src) + + updater := &update.CopyMergeUpdater{} + options := update.Options{ + UpdatedPath: src, + LocalPath: dst, + IsRoot: true, + } + + err = updater.Update(options) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no such file or directory") +} + +func TestCopyMergeErrorUpdatingKptfile(t *testing.T) { + src := t.TempDir() + dst := t.TempDir() + + err := os.WriteFile(filepath.Join(src, "Kptfile"), []byte(` +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: source-package +`), 0644) + assert.NoError(t, err) + + err = os.WriteFile(filepath.Join(dst, "Kptfile"), []byte(` +apiVersion: kpt.dev/v000 +kind: malformedKptfile +`), 0644) + assert.NoError(t, err) + + updater := &update.CopyMergeUpdater{} + options := update.Options{ + UpdatedPath: src, + LocalPath: dst, + IsRoot: true, + } + + err = updater.Update(options) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unknown resource type") +} + +func TestCopyMergeErrorCopyingFile(t *testing.T) { + src := t.TempDir() + dst := t.TempDir() + + err := os.WriteFile(filepath.Join(src, "file.txt"), []byte("content"), 0644) + if err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + err = os.Mkdir(filepath.Join(dst, "file.txt"), 0755) + if err != nil { + t.Fatalf("Failed to create directory: %v", err) + } + + updater := &update.CopyMergeUpdater{} + options := update.Options{ + UpdatedPath: src, + LocalPath: dst, + IsRoot: true, + } + + err = updater.Update(options) + assert.Error(t, err) + assert.Contains(t, err.Error(), "is a directory") +} + +func TestCopyMergeDifferentMetadata(t *testing.T) { + testCases := map[string]struct { + origin *pkgbuilder.RootPkg + local *pkgbuilder.RootPkg + updated *pkgbuilder.RootPkg + relPackagePath string + isRoot bool + expected *pkgbuilder.RootPkg + }{ + "kpt metadata name": { + origin: pkgbuilder.NewRootPkg(), + local: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(), + ), + updated: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(), + ), + relPackagePath: "/", + isRoot: true, + expected: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(), + ), + }, + "sub folder with different kptfile": { + origin: pkgbuilder.NewRootPkg(), + local: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "root", "1", copyMergeLiteral), + ). + WithSubPackages( + pkgbuilder.NewSubPkg("pkgA"). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A1", "1", copyMergeLiteral), + ), + ), + updated: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "root", "2", copyMergeLiteral), + ). + WithSubPackages( + pkgbuilder.NewSubPkg("pkgA"). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A2", "2", copyMergeLiteral), + ), + ), + relPackagePath: "/", + isRoot: true, + expected: pkgbuilder.NewRootPkg(). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "root", "2", copyMergeLiteral), + ). + WithSubPackages( + pkgbuilder.NewSubPkg("pkgA"). + WithKptfile( + pkgbuilder.NewKptfile(). + WithUpstream(kptRepo, "A2", "2", copyMergeLiteral), + ), + ), + }, + } + + for tn, tc := range testCases { + t.Run(tn, func(t *testing.T) { + repos := testutil.EmptyReposInfo + origin := tc.origin.ExpandPkg(t, repos) // metadata.name: "base" + local := tc.local.ExpandPkgWithName(t, "local", repos) // metadata.name: "local" + updated := tc.updated.ExpandPkgWithName(t, "updated", repos) // metadata.name: "updated" + expected := tc.expected.ExpandPkgWithName(t, "local", repos) // metadata.name: "local" I am expeting this field to not change + + updater := &update.CopyMergeUpdater{} + + err := updater.Update(update.Options{ + RelPackagePath: tc.relPackagePath, + OriginPath: filepath.Join(origin, tc.relPackagePath), + LocalPath: filepath.Join(local, tc.relPackagePath), + UpdatedPath: filepath.Join(updated, tc.relPackagePath), + IsRoot: tc.isRoot, + }) + if !assert.NoError(t, err) { + t.FailNow() + } + + testutil.KptfileAwarePkgEqual(t, local, expected, false) + }) + } +} + +func TestCopyMergeErrorRemovingFile(t *testing.T) { + src := t.TempDir() + dst := t.TempDir() + org := t.TempDir() + + // Create a file in org and dst, but not in src (so RemoveStaleItems will try to remove it) + fileName := "file.txt" + filePathDst := filepath.Join(dst, fileName) + filePathOrg := filepath.Join(org, fileName) + + assert.NoError(t, os.WriteFile(filePathDst, []byte("content"), 0644)) + assert.NoError(t, os.WriteFile(filePathOrg, []byte("content"), 0644)) + + assert.NoError(t, os.Remove(filePathDst)) + assert.NoError(t, os.Mkdir(filePathDst, 0755)) + assert.NoError(t, os.WriteFile(filepath.Join(filePathDst, "dummy"), []byte("x"), 0644)) + + updater := &update.CopyMergeUpdater{} + options := update.Options{ + OriginPath: org, + UpdatedPath: src, + LocalPath: dst, + IsRoot: true, + } + + err := updater.Update(options) + assert.Error(t, err) + assert.Contains(t, err.Error(), "directory not empty") +} diff --git a/internal/util/update/update.go b/internal/util/update/update.go index 2d9b4eabe7..240c1834f4 100644 --- a/internal/util/update/update.go +++ b/internal/util/update/update.go @@ -1,4 +1,4 @@ -// Copyright 2019 The kpt Authors +// Copyright 2019,2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -90,6 +90,7 @@ var strategies = map[kptfilev1.UpdateStrategyType]func() Updater{ kptfilev1.FastForward: func() Updater { return FastForwardUpdater{} }, kptfilev1.ForceDeleteReplace: func() Updater { return ReplaceUpdater{} }, kptfilev1.ResourceMerge: func() Updater { return ResourceMergeUpdater{} }, + kptfilev1.CopyMerge: func() Updater { return CopyMergeUpdater{} }, } // Command updates the contents of a local package to a different version. diff --git a/pkg/api/kptfile/v1/types.go b/pkg/api/kptfile/v1/types.go index aa3e971ac5..03df468197 100644 --- a/pkg/api/kptfile/v1/types.go +++ b/pkg/api/kptfile/v1/types.go @@ -1,4 +1,4 @@ -// Copyright 2021 The kpt Authors +// Copyright 2021,2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import ( "sigs.k8s.io/kustomize/kyaml/yaml" ) +//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.19.0 object:headerFile="../../../../rollouts/hack/boilerplate.go.txt" + const ( KptFileName = "Kptfile" @@ -105,6 +107,8 @@ func ToUpdateStrategy(strategy string) (UpdateStrategyType, error) { return FastForward, nil case string(ForceDeleteReplace): return ForceDeleteReplace, nil + case string(CopyMerge): + return CopyMerge, nil default: return "", fmt.Errorf("unknown update strategy %q", strategy) } @@ -119,6 +123,8 @@ const ( FastForward UpdateStrategyType = "fast-forward" // ForceDeleteReplace wipes all local changes to the package. ForceDeleteReplace UpdateStrategyType = "force-delete-replace" + // CopyMerge copies the updated package into the local package. + CopyMerge UpdateStrategyType = "copy-merge" ) // UpdateStrategies is a slice with all the supported update strategies. @@ -126,6 +132,7 @@ var UpdateStrategies = []UpdateStrategyType{ ResourceMerge, FastForward, ForceDeleteReplace, + CopyMerge, } // UpdateStrategiesAsStrings returns a list of update strategies as strings. @@ -396,3 +403,9 @@ const ( ConditionFalse ConditionStatus = "False" ConditionUnknown ConditionStatus = "Unknown" ) + +// BFSRenderAnnotation is an annotation that can be used to indicate that a package +// should be hydrated from the root package to the subpackages in a Breadth-First Level Order manner. +const ( + BFSRenderAnnotation = "kpt.dev/bfs-rendering" +) diff --git a/pkg/api/kptfile/v1/validation.go b/pkg/api/kptfile/v1/validation.go index 08c316da7e..6818938ead 100644 --- a/pkg/api/kptfile/v1/validation.go +++ b/pkg/api/kptfile/v1/validation.go @@ -1,4 +1,4 @@ -// Copyright 2021 The kpt Authors +// Copyright 2021,2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/api/kptfile/v1/validation_test.go b/pkg/api/kptfile/v1/validation_test.go index bb6c9a6bc1..08c5e23d1e 100644 --- a/pkg/api/kptfile/v1/validation_test.go +++ b/pkg/api/kptfile/v1/validation_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 The kpt Authors +// Copyright 2021,2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -179,7 +179,7 @@ func TestValidateFunctionName(t *testing.T) { true, }, { - "ghcr.io/kptdev/krm-functions-catalog/generate-folders:unstable", + "ghcr.io/kptdev/krm-functions-catalog/generate-folders:latest", true, }, { @@ -187,7 +187,7 @@ func TestValidateFunctionName(t *testing.T) { true, }, { - "ghcr.io/kptdev/krm-functions-catalog/generate-folders:latest-alpha1", + "ghcr.io/kptdev/krm-functions-catalog/generate-folders:v1.2.3-alpha1", true, }, { diff --git a/pkg/api/kptfile/v1/zz_generated.deepcopy.go b/pkg/api/kptfile/v1/zz_generated.deepcopy.go index 8818c129b4..922a205073 100644 --- a/pkg/api/kptfile/v1/zz_generated.deepcopy.go +++ b/pkg/api/kptfile/v1/zz_generated.deepcopy.go @@ -1,6 +1,6 @@ //go:build !ignore_autogenerated -// Copyright 2023 The kpt Authors +// Copyright 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/release/formula/main_test.go b/release/formula/main_test.go index 965eee283f..d357a84755 100644 --- a/release/formula/main_test.go +++ b/release/formula/main_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022,2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/rollouts/hack/boilerplate.go.txt b/rollouts/hack/boilerplate.go.txt index 29c55ecda3..ebc8a1d578 100644 --- a/rollouts/hack/boilerplate.go.txt +++ b/rollouts/hack/boilerplate.go.txt @@ -1,15 +1,13 @@ -/* -Copyright 2022. - -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. -*/ \ No newline at end of file +// Copyright YEAR The kpt Authors +// +// 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.