refactor the strategic merge patch transformer toward moving it to a plugin (#1340)

This commit is contained in:
Jingfang Liu
2019-07-11 10:22:56 -07:00
committed by GitHub
parent 7a48b2ba8e
commit 31ab347da2
13 changed files with 168 additions and 121 deletions

View File

@@ -20,6 +20,10 @@ package kunstruct
import (
"encoding/json"
"fmt"
jsonpatch "github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/kustomize/v3/pkg/types"
@@ -285,3 +289,59 @@ func (fs *UnstructAdapter) MatchesAnnotationSelector(selector string) (bool, err
}
return s.Matches(labels.Set(fs.GetAnnotations())), nil
}
func (fs *UnstructAdapter) Patch(patch ifc.Kunstructured) error {
versionedObj, err := scheme.Scheme.New(
toSchemaGvk(patch.GetGvk()))
merged := map[string]interface{}{}
saveName := fs.GetName()
switch {
case runtime.IsNotRegisteredError(err):
baseBytes, err := json.Marshal(fs.Map())
if err != nil {
return err
}
patchBytes, err := json.Marshal(patch.Map())
if err != nil {
return err
}
mergedBytes, err := jsonpatch.MergePatch(baseBytes, patchBytes)
if err != nil {
return err
}
err = json.Unmarshal(mergedBytes, &merged)
if err != nil {
return err
}
case err != nil:
return err
default:
// Use Strategic-Merge-Patch to handle types w/ schema
// TODO: Change this to use the new Merge package.
// Store the name of the target object, because this name may have been munged.
// Apply this name to the patched object.
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
if err != nil {
return err
}
merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(
fs.Map(),
patch.Map(),
lookupPatchMeta)
if err != nil {
return err
}
}
fs.SetMap(merged)
fs.SetName(saveName)
return nil
}
// toSchemaGvk converts to a schema.GroupVersionKind.
func toSchemaGvk(x gvk.Gvk) schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: x.Group,
Version: x.Version,
Kind: x.Kind,
}
}

View File

@@ -6,6 +6,7 @@ package transformer
import (
"sigs.k8s.io/kustomize/v3/k8sdeps/transformer/patch"
"sigs.k8s.io/kustomize/v3/pkg/resmap"
"sigs.k8s.io/kustomize/v3/pkg/resource"
"sigs.k8s.io/kustomize/v3/pkg/transformers"
)
@@ -18,6 +19,12 @@ func NewFactoryImpl() *FactoryImpl {
return &FactoryImpl{}
}
func (p *FactoryImpl) MergePatches(patches []*resource.Resource,
rf *resource.Factory) (
resmap.ResMap, error) {
return patch.MergePatches(patches, rf)
}
// MakePatchTransformer makes a new patch transformer
func (p *FactoryImpl) MakePatchTransformer(
slice []*resource.Resource,

View File

@@ -5,6 +5,11 @@ package patch
import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/kustomize/v3/pkg/gvk"
"sigs.k8s.io/kustomize/v3/pkg/resmap"
"github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime"
@@ -122,3 +127,64 @@ func (smp *strategicMergePatch) mergePatches(patch1, patch2 *resource.Resource)
smp.lookupPatchMeta, patch1.Map(), patch2.Map())
return smp.rf.FromMap(mergeJSONMap), err
}
// mergePatches merge and index patches by OrgId.
// It errors out if there is conflict between patches.
func MergePatches(patches []*resource.Resource,
rf *resource.Factory) (resmap.ResMap, error) {
rc := resmap.New()
for ix, patch := range patches {
id := patch.OrgId()
existing := rc.GetMatchingResourcesByOriginalId(id.GvknEquals)
if len(existing) == 0 {
rc.Append(patch)
continue
}
if len(existing) > 1 {
return nil, fmt.Errorf("self conflict in patches")
}
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk))
if err != nil && !runtime.IsNotRegisteredError(err) {
return nil, err
}
var cd conflictDetector
if err != nil {
cd = newJMPConflictDetector(rf)
} else {
cd, err = newSMPConflictDetector(versionedObj, rf)
if err != nil {
return nil, err
}
}
conflict, err := cd.hasConflict(existing[0], patch)
if err != nil {
return nil, err
}
if conflict {
conflictingPatch, err := cd.findConflict(ix, patches)
if err != nil {
return nil, err
}
return nil, fmt.Errorf(
"conflict between %#v and %#v",
conflictingPatch.Map(), patch.Map())
}
merged, err := cd.mergePatches(existing[0], patch)
if err != nil {
return nil, err
}
rc.Replace(merged)
}
return rc, nil
}
// toSchemaGvk converts to a schema.GroupVersionKind.
func toSchemaGvk(x gvk.Gvk) schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: x.Group,
Version: x.Version,
Kind: x.Kind,
}
}

View File

@@ -4,15 +4,6 @@
package patch
import (
"encoding/json"
"fmt"
"github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/kustomize/v3/pkg/gvk"
"sigs.k8s.io/kustomize/v3/pkg/resmap"
"sigs.k8s.io/kustomize/v3/pkg/resource"
"sigs.k8s.io/kustomize/v3/pkg/transformers"
@@ -38,7 +29,7 @@ func NewTransformer(
// Transform apply the patches on top of the base resources.
// nolint:ineffassign
func (tf *transformer) Transform(m resmap.ResMap) error {
patches, err := tf.mergePatches()
patches, err := MergePatches(tf.patches, tf.rf)
if err != nil {
return err
}
@@ -47,110 +38,10 @@ func (tf *transformer) Transform(m resmap.ResMap) error {
if err != nil {
return err
}
merged := map[string]interface{}{}
versionedObj, err := scheme.Scheme.New(
toSchemaGvk(patch.OrgId().Gvk))
saveName := target.GetName()
switch {
case runtime.IsNotRegisteredError(err):
// Use JSON merge patch to handle types w/o schema
baseBytes, err := json.Marshal(target.Map())
if err != nil {
return err
}
patchBytes, err := json.Marshal(patch.Map())
if err != nil {
return err
}
mergedBytes, err := jsonpatch.MergePatch(baseBytes, patchBytes)
if err != nil {
return err
}
err = json.Unmarshal(mergedBytes, &merged)
if err != nil {
return err
}
case err != nil:
err = target.Patch(patch.Kunstructured)
if err != nil {
return err
default:
// Use Strategic-Merge-Patch to handle types w/ schema
// TODO: Change this to use the new Merge package.
// Store the name of the target object, because this name may have been munged.
// Apply this name to the patched object.
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
if err != nil {
return err
}
merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(
target.Map(),
patch.Map(),
lookupPatchMeta)
if err != nil {
return err
}
}
target.SetMap(merged)
target.SetName(saveName)
}
return nil
}
// mergePatches merge and index patches by OrgId.
// It errors out if there is conflict between patches.
func (tf *transformer) mergePatches() (resmap.ResMap, error) {
rc := resmap.New()
for ix, patch := range tf.patches {
id := patch.OrgId()
existing := rc.GetMatchingResourcesByOriginalId(id.GvknEquals)
if len(existing) == 0 {
rc.Append(patch)
continue
}
if len(existing) > 1 {
return nil, fmt.Errorf("self conflict in patches")
}
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk))
if err != nil && !runtime.IsNotRegisteredError(err) {
return nil, err
}
var cd conflictDetector
if err != nil {
cd = newJMPConflictDetector(tf.rf)
} else {
cd, err = newSMPConflictDetector(versionedObj, tf.rf)
if err != nil {
return nil, err
}
}
conflict, err := cd.hasConflict(existing[0], patch)
if err != nil {
return nil, err
}
if conflict {
conflictingPatch, err := cd.findConflict(ix, tf.patches)
if err != nil {
return nil, err
}
return nil, fmt.Errorf(
"conflict between %#v and %#v",
conflictingPatch.Map(), patch.Map())
}
merged, err := cd.mergePatches(existing[0], patch)
if err != nil {
return nil, err
}
rc.Replace(merged)
}
return rc, nil
}
// toSchemaGvk converts to a schema.GroupVersionKind.
func toSchemaGvk(x gvk.Gvk) schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: x.Group,
Version: x.Version,
Kind: x.Kind,
}
}