mirror of https://github.com/k3s-io/k3s
124 lines
3.2 KiB
Go
124 lines
3.2 KiB
Go
// Copyright 2020 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package resmap
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"sigs.k8s.io/kustomize/api/resource"
|
|
)
|
|
|
|
// merginator coordinates merging the resources in incoming to the result.
|
|
type merginator struct {
|
|
incoming []*resource.Resource
|
|
cdf resource.ConflictDetectorFactory
|
|
result ResMap
|
|
}
|
|
|
|
func (m *merginator) ConflatePatches(in []*resource.Resource) (ResMap, error) {
|
|
m.result = New()
|
|
m.incoming = in
|
|
for index := range m.incoming {
|
|
alreadyInResult, err := m.appendIfNoMatch(index)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if alreadyInResult != nil {
|
|
// The resource at index has the same resId as a previously
|
|
// considered resource.
|
|
//
|
|
// If they conflict with each other (e.g. they both want to change
|
|
// the image name in a Deployment, but to different values),
|
|
// return an error.
|
|
//
|
|
// If they don't conflict, then merge them into a single resource,
|
|
// since they both target the same item, and we want cumulative
|
|
// behavior. E.g. say both patches modify a map. Without a merge,
|
|
// the last patch wins, replacing the entire map.
|
|
err = m.mergeWithExisting(index, alreadyInResult)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
return m.result, nil
|
|
}
|
|
|
|
func (m *merginator) appendIfNoMatch(index int) (*resource.Resource, error) {
|
|
candidate := m.incoming[index]
|
|
matchedResources := m.result.GetMatchingResourcesByAnyId(
|
|
candidate.OrgId().Equals)
|
|
if len(matchedResources) == 0 {
|
|
m.result.Append(candidate)
|
|
return nil, nil
|
|
}
|
|
if len(matchedResources) > 1 {
|
|
return nil, fmt.Errorf("multiple resources targeted by patch")
|
|
}
|
|
return matchedResources[0], nil
|
|
}
|
|
|
|
func (m *merginator) mergeWithExisting(
|
|
index int, alreadyInResult *resource.Resource) error {
|
|
candidate := m.incoming[index]
|
|
cd, err := m.cdf.New(candidate.OrgId().Gvk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hasConflict, err := cd.HasConflict(candidate, alreadyInResult)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if hasConflict {
|
|
return m.makeError(cd, index)
|
|
}
|
|
merged, err := cd.MergePatches(alreadyInResult, candidate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = m.result.Replace(merged)
|
|
return err
|
|
}
|
|
|
|
// Make an error message describing the conflict.
|
|
func (m *merginator) makeError(cd resource.ConflictDetector, index int) error {
|
|
conflict, err := m.findConflict(cd, index)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if conflict == nil {
|
|
return fmt.Errorf("expected conflict for %s", m.incoming[index].OrgId())
|
|
}
|
|
conflictMap, _ := conflict.Map()
|
|
incomingIndexMap, _ := m.incoming[index].Map()
|
|
return fmt.Errorf(
|
|
"conflict between %#v at index %d and %#v",
|
|
incomingIndexMap,
|
|
index,
|
|
conflictMap,
|
|
)
|
|
}
|
|
|
|
// findConflict looks for a conflict in a resource slice.
|
|
// It returns the first conflict between the resource at index
|
|
// and some other resource. Two resources can only conflict if
|
|
// they have the same original ResId.
|
|
func (m *merginator) findConflict(
|
|
cd resource.ConflictDetector, index int) (*resource.Resource, error) {
|
|
targetId := m.incoming[index].OrgId()
|
|
for i, p := range m.incoming {
|
|
if i == index || !targetId.Equals(p.OrgId()) {
|
|
continue
|
|
}
|
|
conflict, err := cd.HasConflict(p, m.incoming[index])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if conflict {
|
|
return p, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|