diff --git a/k8sdeps/kunstruct/kunstruct.go b/k8sdeps/kunstruct/kunstruct.go index dc3a100d6..dd854b557 100644 --- a/k8sdeps/kunstruct/kunstruct.go +++ b/k8sdeps/kunstruct/kunstruct.go @@ -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, + } +} diff --git a/k8sdeps/transformer/factory.go b/k8sdeps/transformer/factory.go index ce071b157..012913b32 100644 --- a/k8sdeps/transformer/factory.go +++ b/k8sdeps/transformer/factory.go @@ -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, diff --git a/k8sdeps/transformer/patch/conflictdetector.go b/k8sdeps/transformer/patch/conflictdetector.go index 42adb3c8e..f575dfa0f 100644 --- a/k8sdeps/transformer/patch/conflictdetector.go +++ b/k8sdeps/transformer/patch/conflictdetector.go @@ -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, + } +} diff --git a/k8sdeps/transformer/patch/transformer.go b/k8sdeps/transformer/patch/transformer.go index 692b0b75e..dd45720f7 100644 --- a/k8sdeps/transformer/patch/transformer.go +++ b/k8sdeps/transformer/patch/transformer.go @@ -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, - } -} diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index 51b8f2875..c2de72413 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -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), diff --git a/pkg/ifc/ifc.go b/pkg/ifc/ifc.go index fff4bdb0d..188e67c60 100644 --- a/pkg/ifc/ifc.go +++ b/pkg/ifc/ifc.go @@ -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. diff --git a/pkg/kusttest/kusttestharness.go b/pkg/kusttest/kusttestharness.go index 2e5adf943..528e82a36 100644 --- a/pkg/kusttest/kusttestharness.go +++ b/pkg/kusttest/kusttestharness.go @@ -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, diff --git a/pkg/plugins/execplugin_test.go b/pkg/plugins/execplugin_test.go index e9dfedf07..7dc7708d2 100644 --- a/pkg/plugins/execplugin_test.go +++ b/pkg/plugins/execplugin_test.go @@ -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{}{ diff --git a/pkg/plugins/loader_test.go b/pkg/plugins/loader_test.go index 7fd5ec9d1..05b175818 100644 --- a/pkg/plugins/loader_test.go +++ b/pkg/plugins/loader_test.go @@ -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 { diff --git a/pkg/resmap/factory.go b/pkg/resmap/factory.go index fb80aa935..ebe44752e 100644 --- a/pkg/resmap/factory.go +++ b/pkg/resmap/factory.go @@ -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. @@ -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 { diff --git a/pkg/resmap/patchfactory.go b/pkg/resmap/patchfactory.go new file mode 100644 index 000000000..2c082c328 --- /dev/null +++ b/pkg/resmap/patchfactory.go @@ -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) +} diff --git a/pkg/resmap/resmap_test.go b/pkg/resmap/resmap_test.go index cb8742599..d607d8485 100644 --- a/pkg/resmap/resmap_test.go +++ b/pkg/resmap/resmap_test.go @@ -19,7 +19,7 @@ import ( var rf = resource.NewFactory( kunstruct.NewKunstructuredFactoryImpl()) -var rmF = NewFactory(rf) +var rmF = NewFactory(rf, nil) func doAppend(t *testing.T, w ResMap, r *resource.Resource) { err := w.Append(r) diff --git a/pkg/target/plugindir_test.go b/pkg/target/plugindir_test.go index 5e55884aa..11b03b4e6 100644 --- a/pkg/target/plugindir_test.go +++ b/pkg/target/plugindir_test.go @@ -62,7 +62,7 @@ metadata: t.Fatalf("Err: %v", err) } rf := resmap.NewFactory(resource.NewFactory( - kunstruct.NewKunstructuredFactoryImpl())) + kunstruct.NewKunstructuredFactoryImpl()), nil) pl := plugins.NewLoader(plugins.ActivePluginConfig(), rf) tg, err := target.NewKustTarget(ldr, rf, transformer.NewFactoryImpl(), pl)