mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
Support multiple implementations for IsSameResource
This commit is contained in:
@@ -19,17 +19,24 @@ const (
|
|||||||
mergeSourceDest = "dest"
|
mergeSourceDest = "dest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ResourceMatcher interface is used to match two resources based on IsSameResource implementation
|
||||||
|
// This is the way to group same logical resources in upstream, local and origin for merge
|
||||||
|
// The default way to group them is using GVKNN similar to how kubernetes server identifies resources
|
||||||
|
// Users of this library might have their own interpretation of grouping similar resources
|
||||||
|
// for e.g. if consumer adds a name-prefix to local resource, it should not be treated as new resource
|
||||||
|
// for updates etc.
|
||||||
|
// Hence, the callers of this library may pass different implementation for IsSameResource
|
||||||
|
type ResourceMatcher interface {
|
||||||
|
IsSameResource(node1, node2 *yaml.RNode) bool
|
||||||
|
}
|
||||||
|
|
||||||
// Merge3 performs a 3-way merge on the original, updated, and destination packages.
|
// Merge3 performs a 3-way merge on the original, updated, and destination packages.
|
||||||
type Merge3 struct {
|
type Merge3 struct {
|
||||||
OriginalPath string
|
OriginalPath string
|
||||||
UpdatedPath string
|
UpdatedPath string
|
||||||
DestPath string
|
DestPath string
|
||||||
MatchFilesGlob []string
|
MatchFilesGlob []string
|
||||||
|
Matcher ResourceMatcher
|
||||||
// MergeOnPath will use the relative filepath as part of the merge key.
|
|
||||||
// This may be necessary if the directory contains multiple copies of
|
|
||||||
// the same resource, or resources patches.
|
|
||||||
MergeOnPath bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Merge3) Merge() error {
|
func (m Merge3) Merge() error {
|
||||||
@@ -67,7 +74,11 @@ func (m Merge3) Merge() error {
|
|||||||
// Filter combines Resources with the same GVK + N + NS into tuples, and then merges them
|
// Filter combines Resources with the same GVK + N + NS into tuples, and then merges them
|
||||||
func (m Merge3) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
func (m Merge3) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
// index the nodes by their identity
|
// index the nodes by their identity
|
||||||
tl := tuples{mergeOnPath: m.MergeOnPath}
|
matcher := m.Matcher
|
||||||
|
if matcher == nil {
|
||||||
|
matcher = &DefaultGVKNNMatcher{MergeOnPath: true}
|
||||||
|
}
|
||||||
|
tl := tuples{matcher: matcher}
|
||||||
for i := range nodes {
|
for i := range nodes {
|
||||||
if err := tl.add(nodes[i]); err != nil {
|
if err := tl.add(nodes[i]); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -82,7 +93,7 @@ func (m Merge3) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|||||||
case t.original == nil && t.updated == nil && t.dest != nil:
|
case t.original == nil && t.updated == nil && t.dest != nil:
|
||||||
// added locally -- keep dest
|
// added locally -- keep dest
|
||||||
output = append(output, t.dest)
|
output = append(output, t.dest)
|
||||||
case t.original == nil && t.updated != nil && t.dest == nil:
|
case t.updated != nil && t.dest == nil:
|
||||||
// added in the update -- add update
|
// added in the update -- add update
|
||||||
output = append(output, t.updated)
|
output = append(output, t.updated)
|
||||||
case t.original != nil && t.updated == nil:
|
case t.original != nil && t.updated == nil:
|
||||||
@@ -109,13 +120,35 @@ func (m Merge3) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|||||||
type tuples struct {
|
type tuples struct {
|
||||||
list []*tuple
|
list []*tuple
|
||||||
|
|
||||||
// mergeOnPath if set to true will use the resource filepath
|
// matcher matches the resources for merge
|
||||||
// as part of the merge key
|
matcher ResourceMatcher
|
||||||
mergeOnPath bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isSameResource returns true if meta1 and meta2 are for the same logic resource
|
// DefaultGVKNNMatcher holds the default matching of resources implementation based on
|
||||||
func (ts *tuples) isSameResource(meta1, meta2 yaml.ResourceMeta) bool {
|
// Group, Version, Kind, Name and Namespace of the resource
|
||||||
|
type DefaultGVKNNMatcher struct {
|
||||||
|
// MergeOnPath will use the relative filepath as part of the merge key.
|
||||||
|
// This may be necessary if the directory contains multiple copies of
|
||||||
|
// the same resource, or resources patches.
|
||||||
|
MergeOnPath bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSameResource returns true if metadata of node1 and metadata of node2 belongs to same logical resource
|
||||||
|
func (dm *DefaultGVKNNMatcher) IsSameResource(node1, node2 *yaml.RNode) bool {
|
||||||
|
if node1 == nil || node2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
meta1, err := node1.GetMeta()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
meta2, err := node2.GetMeta()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if meta1.Name != meta2.Name {
|
if meta1.Name != meta2.Name {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -128,7 +161,7 @@ func (ts *tuples) isSameResource(meta1, meta2 yaml.ResourceMeta) bool {
|
|||||||
if meta1.Kind != meta2.Kind {
|
if meta1.Kind != meta2.Kind {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if ts.mergeOnPath {
|
if dm.MergeOnPath {
|
||||||
// directories may contain multiple copies of a resource with the same
|
// directories may contain multiple copies of a resource with the same
|
||||||
// name, namespace, apiVersion and kind -- e.g. kustomize patches, or
|
// name, namespace, apiVersion and kind -- e.g. kustomize patches, or
|
||||||
// multiple environments
|
// multiple environments
|
||||||
@@ -143,17 +176,13 @@ func (ts *tuples) isSameResource(meta1, meta2 yaml.ResourceMeta) bool {
|
|||||||
|
|
||||||
// add adds a node to the list, combining it with an existing matching Resource if found
|
// add adds a node to the list, combining it with an existing matching Resource if found
|
||||||
func (ts *tuples) add(node *yaml.RNode) error {
|
func (ts *tuples) add(node *yaml.RNode) error {
|
||||||
nodeMeta, err := node.GetMeta()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i := range ts.list {
|
for i := range ts.list {
|
||||||
t := ts.list[i]
|
t := ts.list[i]
|
||||||
if ts.isSameResource(t.meta, nodeMeta) {
|
if ts.matcher.IsSameResource(addedNode(t), node) {
|
||||||
return t.add(node)
|
return t.add(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t := &tuple{meta: nodeMeta}
|
t := &tuple{}
|
||||||
if err := t.add(node); err != nil {
|
if err := t.add(node); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -161,9 +190,19 @@ func (ts *tuples) add(node *yaml.RNode) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addedNode returns one on the existing added nodes in the tuple
|
||||||
|
func addedNode(t *tuple) *yaml.RNode {
|
||||||
|
if t.updated != nil {
|
||||||
|
return t.updated
|
||||||
|
}
|
||||||
|
if t.original != nil {
|
||||||
|
return t.original
|
||||||
|
}
|
||||||
|
return t.dest
|
||||||
|
}
|
||||||
|
|
||||||
// tuple wraps an original, updated, and dest tuple for a given Resource
|
// tuple wraps an original, updated, and dest tuple for a given Resource
|
||||||
type tuple struct {
|
type tuple struct {
|
||||||
meta yaml.ResourceMeta
|
|
||||||
original *yaml.RNode
|
original *yaml.RNode
|
||||||
updated *yaml.RNode
|
updated *yaml.RNode
|
||||||
dest *yaml.RNode
|
dest *yaml.RNode
|
||||||
@@ -178,17 +217,17 @@ func (t *tuple) add(node *yaml.RNode) error {
|
|||||||
switch meta.Annotations[mergeSourceAnnotation] {
|
switch meta.Annotations[mergeSourceAnnotation] {
|
||||||
case mergeSourceDest:
|
case mergeSourceDest:
|
||||||
if t.dest != nil {
|
if t.dest != nil {
|
||||||
return fmt.Errorf("dest source already specified")
|
return duplicateError("local", meta.Annotations[kioutil.PathAnnotation])
|
||||||
}
|
}
|
||||||
t.dest = node
|
t.dest = node
|
||||||
case mergeSourceOriginal:
|
case mergeSourceOriginal:
|
||||||
if t.original != nil {
|
if t.original != nil {
|
||||||
return fmt.Errorf("original source already specified")
|
return duplicateError("original upstream", meta.Annotations[kioutil.PathAnnotation])
|
||||||
}
|
}
|
||||||
t.original = node
|
t.original = node
|
||||||
case mergeSourceUpdated:
|
case mergeSourceUpdated:
|
||||||
if t.updated != nil {
|
if t.updated != nil {
|
||||||
return fmt.Errorf("updated source already specified")
|
return duplicateError("updated upstream", meta.Annotations[kioutil.PathAnnotation])
|
||||||
}
|
}
|
||||||
t.updated = node
|
t.updated = node
|
||||||
default:
|
default:
|
||||||
@@ -201,3 +240,8 @@ func (t *tuple) add(node *yaml.RNode) error {
|
|||||||
func (t *tuple) merge() (*yaml.RNode, error) {
|
func (t *tuple) merge() (*yaml.RNode, error) {
|
||||||
return merge3.Merge(t.dest, t.original, t.updated)
|
return merge3.Merge(t.dest, t.original, t.updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// duplicateError returns duplicate resources error
|
||||||
|
func duplicateError(source, filePath string) error {
|
||||||
|
return fmt.Errorf(`found duplicate %q resources in file %q, please refer to "update" documentation for the fix`, source, filePath)
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func TestMerge3_Merge(t *testing.T) {
|
|||||||
OriginalPath: filepath.Join(datadir, "dataset1"),
|
OriginalPath: filepath.Join(datadir, "dataset1"),
|
||||||
UpdatedPath: filepath.Join(datadir, "dataset1-remoteupdates"),
|
UpdatedPath: filepath.Join(datadir, "dataset1-remoteupdates"),
|
||||||
DestPath: filepath.Join(dir, "dataset1"),
|
DestPath: filepath.Join(dir, "dataset1"),
|
||||||
|
Matcher: &filters.DefaultGVKNNMatcher{MergeOnPath: false},
|
||||||
}.Merge()
|
}.Merge()
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@@ -89,7 +90,6 @@ func TestMerge3_Merge_path(t *testing.T) {
|
|||||||
OriginalPath: filepath.Join(datadir, "dataset1"),
|
OriginalPath: filepath.Join(datadir, "dataset1"),
|
||||||
UpdatedPath: filepath.Join(datadir, "dataset1-remoteupdates"),
|
UpdatedPath: filepath.Join(datadir, "dataset1-remoteupdates"),
|
||||||
DestPath: filepath.Join(dir, "dataset1"),
|
DestPath: filepath.Join(dir, "dataset1"),
|
||||||
MergeOnPath: true,
|
|
||||||
}.Merge()
|
}.Merge()
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@@ -135,6 +135,7 @@ func TestMerge3_Merge_fail(t *testing.T) {
|
|||||||
OriginalPath: filepath.Join(datadir, "dataset1"),
|
OriginalPath: filepath.Join(datadir, "dataset1"),
|
||||||
UpdatedPath: filepath.Join(datadir, "dataset1-remoteupdates"),
|
UpdatedPath: filepath.Join(datadir, "dataset1-remoteupdates"),
|
||||||
DestPath: filepath.Join(dir, "dataset1"),
|
DestPath: filepath.Join(dir, "dataset1"),
|
||||||
|
Matcher: &filters.DefaultGVKNNMatcher{MergeOnPath: false},
|
||||||
}.Merge()
|
}.Merge()
|
||||||
if !assert.Error(t, err) {
|
if !assert.Error(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
|||||||
Reference in New Issue
Block a user