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 ( import (
"encoding/json" "encoding/json"
"fmt" "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" "k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/kustomize/v3/pkg/types" "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 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 ( import (
"sigs.k8s.io/kustomize/v3/k8sdeps/transformer/patch" "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/resource"
"sigs.k8s.io/kustomize/v3/pkg/transformers" "sigs.k8s.io/kustomize/v3/pkg/transformers"
) )
@@ -18,6 +19,12 @@ func NewFactoryImpl() *FactoryImpl {
return &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 // MakePatchTransformer makes a new patch transformer
func (p *FactoryImpl) MakePatchTransformer( func (p *FactoryImpl) MakePatchTransformer(
slice []*resource.Resource, slice []*resource.Resource,

View File

@@ -5,6 +5,11 @@ package patch
import ( import (
"encoding/json" "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" "github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@@ -122,3 +127,64 @@ func (smp *strategicMergePatch) mergePatches(patch1, patch2 *resource.Resource)
smp.lookupPatchMeta, patch1.Map(), patch2.Map()) smp.lookupPatchMeta, patch1.Map(), patch2.Map())
return smp.rf.FromMap(mergeJSONMap), err 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 package patch
import ( 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/resmap"
"sigs.k8s.io/kustomize/v3/pkg/resource" "sigs.k8s.io/kustomize/v3/pkg/resource"
"sigs.k8s.io/kustomize/v3/pkg/transformers" "sigs.k8s.io/kustomize/v3/pkg/transformers"
@@ -38,7 +29,7 @@ func NewTransformer(
// Transform apply the patches on top of the base resources. // Transform apply the patches on top of the base resources.
// nolint:ineffassign // nolint:ineffassign
func (tf *transformer) Transform(m resmap.ResMap) error { func (tf *transformer) Transform(m resmap.ResMap) error {
patches, err := tf.mergePatches() patches, err := MergePatches(tf.patches, tf.rf)
if err != nil { if err != nil {
return err return err
} }
@@ -47,110 +38,10 @@ func (tf *transformer) Transform(m resmap.ResMap) error {
if err != nil { if err != nil {
return err return err
} }
merged := map[string]interface{}{} err = target.Patch(patch.Kunstructured)
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 { if err != nil {
return err 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(
target.Map(),
patch.Map(),
lookupPatchMeta)
if err != nil {
return err
}
}
target.SetMap(merged)
target.SetName(saveName)
} }
return nil 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,
}
}

View File

@@ -36,12 +36,13 @@ See https://sigs.k8s.io/kustomize
} }
uf := kunstruct.NewKunstructuredFactoryImpl() uf := kunstruct.NewKunstructuredFactoryImpl()
rf := resmap.NewFactory(resource.NewFactory(uf)) pf := transformer.NewFactoryImpl()
rf := resmap.NewFactory(resource.NewFactory(uf), pf)
v := validator.NewKustValidator() v := validator.NewKustValidator()
c.AddCommand( c.AddCommand(
build.NewCmdBuild( build.NewCmdBuild(
stdOut, fSys, v, stdOut, fSys, v,
rf, transformer.NewFactoryImpl()), rf, pf),
edit.NewCmdEdit(fSys, v, uf), edit.NewCmdEdit(fSys, v, uf),
misc.NewCmdConfig(fSys), misc.NewCmdConfig(fSys),
misc.NewCmdVersion(stdOut), misc.NewCmdVersion(stdOut),

View File

@@ -64,6 +64,7 @@ type Kunstructured interface {
SetAnnotations(map[string]string) SetAnnotations(map[string]string)
MatchesLabelSelector(selector string) (bool, error) MatchesLabelSelector(selector string) (bool, error)
MatchesAnnotationSelector(selector string) (bool, error) MatchesAnnotationSelector(selector string) (bool, error)
Patch(Kunstructured) error
} }
// KunstructuredFactory makes instances of Kunstructured. // KunstructuredFactory makes instances of Kunstructured.

View File

@@ -49,7 +49,7 @@ func NewKustTestHarnessFull(
t *testing.T, path string, t *testing.T, path string,
lr loader.LoadRestrictorFunc, pc *types.PluginConfig) *KustTestHarness { lr loader.LoadRestrictorFunc, pc *types.PluginConfig) *KustTestHarness {
rf := resmap.NewFactory(resource.NewFactory( rf := resmap.NewFactory(resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())) kunstruct.NewKunstructuredFactoryImpl()), nil)
return &KustTestHarness{ return &KustTestHarness{
t: t, t: t,
rf: rf, rf: rf,

View File

@@ -30,7 +30,7 @@ func TestExecPluginConfig(t *testing.T) {
path := "/app" path := "/app"
rf := resmap.NewFactory( rf := resmap.NewFactory(
resource.NewFactory( resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())) kunstruct.NewKunstructuredFactoryImpl()), nil)
ldr := loadertest.NewFakeLoader(path) ldr := loadertest.NewFakeLoader(path)
pluginConfig := rf.RF().FromMap( pluginConfig := rf.RF().FromMap(
map[string]interface{}{ map[string]interface{}{

View File

@@ -50,7 +50,7 @@ func TestLoader(t *testing.T) {
"someteam.example.com", "v1", "SomeServiceGenerator") "someteam.example.com", "v1", "SomeServiceGenerator")
rmF := resmap.NewFactory(resource.NewFactory( rmF := resmap.NewFactory(resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())) kunstruct.NewKunstructuredFactoryImpl()), nil)
l := NewLoader(ActivePluginConfig(), rmF) l := NewLoader(ActivePluginConfig(), rmF)
if l == nil { if l == nil {

View File

@@ -14,11 +14,12 @@ import (
// Factory makes instances of ResMap. // Factory makes instances of ResMap.
type Factory struct { type Factory struct {
resF *resource.Factory resF *resource.Factory
tf PatchFactory
} }
// NewFactory returns a new resmap.Factory. // NewFactory returns a new resmap.Factory.
func NewFactory(rf *resource.Factory) *Factory { func NewFactory(rf *resource.Factory, tf PatchFactory) *Factory {
return &Factory{resF: rf} return &Factory{resF: rf, tf: tf}
} }
// RF returns a resource.Factory. // RF returns a resource.Factory.
@@ -118,6 +119,11 @@ func (rmF *Factory) FromSecretArgs(
return rmF.FromResource(res), nil return rmF.FromResource(res), nil
} }
func (rmF *Factory) MergePatches(patches []*resource.Resource) (
ResMap, error) {
return rmF.tf.MergePatches(patches, rmF.resF)
}
func newResMapFromResourceSlice(resources []*resource.Resource) (ResMap, error) { func newResMapFromResourceSlice(resources []*resource.Resource) (ResMap, error) {
result := New() result := New()
for _, res := range resources { for _, res := range resources {

View File

@@ -0,0 +1,15 @@
/// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package patch holds miscellaneous interfaces used by kustomize.
package resmap
import (
"sigs.k8s.io/kustomize/v3/pkg/resource"
)
// PatchFactory makes transformers that require k8sdeps.
type PatchFactory interface {
MergePatches(patches []*resource.Resource,
rf *resource.Factory) (ResMap, error)
}

View File

@@ -19,7 +19,7 @@ import (
var rf = resource.NewFactory( var rf = resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl()) kunstruct.NewKunstructuredFactoryImpl())
var rmF = NewFactory(rf) var rmF = NewFactory(rf, nil)
func doAppend(t *testing.T, w ResMap, r *resource.Resource) { func doAppend(t *testing.T, w ResMap, r *resource.Resource) {
err := w.Append(r) err := w.Append(r)

View File

@@ -62,7 +62,7 @@ metadata:
t.Fatalf("Err: %v", err) t.Fatalf("Err: %v", err)
} }
rf := resmap.NewFactory(resource.NewFactory( rf := resmap.NewFactory(resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())) kunstruct.NewKunstructuredFactoryImpl()), nil)
pl := plugins.NewLoader(plugins.ActivePluginConfig(), rf) pl := plugins.NewLoader(plugins.ActivePluginConfig(), rf)
tg, err := target.NewKustTarget(ldr, rf, transformer.NewFactoryImpl(), pl) tg, err := target.NewKustTarget(ldr, rf, transformer.NewFactoryImpl(), pl)