Skip to content

Commit

Permalink
refactor the strategic merge patch transformer toward moving it to a …
Browse files Browse the repository at this point in the history
…plugin (#1340)
  • Loading branch information
Liujingfang1 committed Jul 11, 2019
1 parent 7a48b2b commit 31ab347
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 121 deletions.
60 changes: 60 additions & 0 deletions k8sdeps/kunstruct/kunstruct.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
}
}
7 changes: 7 additions & 0 deletions k8sdeps/transformer/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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,
Expand Down
66 changes: 66 additions & 0 deletions k8sdeps/transformer/patch/conflictdetector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
}
}
115 changes: 3 additions & 112 deletions k8sdeps/transformer/patch/transformer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand All @@ -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,
}
}
5 changes: 3 additions & 2 deletions pkg/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ See https://sigs.k8s.io/kustomize
}

uf := kunstruct.NewKunstructuredFactoryImpl()
rf := resmap.NewFactory(resource.NewFactory(uf))
pf := transformer.NewFactoryImpl()
rf := resmap.NewFactory(resource.NewFactory(uf), pf)
v := validator.NewKustValidator()
c.AddCommand(
build.NewCmdBuild(
stdOut, fSys, v,
rf, transformer.NewFactoryImpl()),
rf, pf),
edit.NewCmdEdit(fSys, v, uf),
misc.NewCmdConfig(fSys),
misc.NewCmdVersion(stdOut),
Expand Down
1 change: 1 addition & 0 deletions pkg/ifc/ifc.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type Kunstructured interface {
SetAnnotations(map[string]string)
MatchesLabelSelector(selector string) (bool, error)
MatchesAnnotationSelector(selector string) (bool, error)
Patch(Kunstructured) error
}

// KunstructuredFactory makes instances of Kunstructured.
Expand Down
2 changes: 1 addition & 1 deletion pkg/kusttest/kusttestharness.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func NewKustTestHarnessFull(
t *testing.T, path string,
lr loader.LoadRestrictorFunc, pc *types.PluginConfig) *KustTestHarness {
rf := resmap.NewFactory(resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl()))
kunstruct.NewKunstructuredFactoryImpl()), nil)
return &KustTestHarness{
t: t,
rf: rf,
Expand Down
2 changes: 1 addition & 1 deletion pkg/plugins/execplugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestExecPluginConfig(t *testing.T) {
path := "/app"
rf := resmap.NewFactory(
resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl()))
kunstruct.NewKunstructuredFactoryImpl()), nil)
ldr := loadertest.NewFakeLoader(path)
pluginConfig := rf.RF().FromMap(
map[string]interface{}{
Expand Down
2 changes: 1 addition & 1 deletion pkg/plugins/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestLoader(t *testing.T) {
"someteam.example.com", "v1", "SomeServiceGenerator")

rmF := resmap.NewFactory(resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl()))
kunstruct.NewKunstructuredFactoryImpl()), nil)

l := NewLoader(ActivePluginConfig(), rmF)
if l == nil {
Expand Down
10 changes: 8 additions & 2 deletions pkg/resmap/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import (
// Factory makes instances of ResMap.
type Factory struct {
resF *resource.Factory
tf PatchFactory
}

// NewFactory returns a new resmap.Factory.
func NewFactory(rf *resource.Factory) *Factory {
return &Factory{resF: rf}
func NewFactory(rf *resource.Factory, tf PatchFactory) *Factory {
return &Factory{resF: rf, tf: tf}
}

// RF returns a resource.Factory.
Expand Down Expand Up @@ -118,6 +119,11 @@ func (rmF *Factory) FromSecretArgs(
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) {
result := New()
for _, res := range resources {
Expand Down

0 comments on commit 31ab347

Please sign in to comment.