From 58bc4b14a2a85576e9e7350c9e9c3e11e5c8356d Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Thu, 26 Jul 2018 14:02:13 -0700 Subject: [PATCH] Add support for using common base --- pkg/app/application.go | 2 +- pkg/app/application_test.go | 10 +++---- pkg/diff/rundiff.go | 2 +- pkg/resmap/resmap.go | 32 ++++++++++++++++++--- pkg/resource/resid.go | 43 ++++++++++++++++++++++++++++- pkg/transformers/patch.go | 9 ++++-- pkg/transformers/prefixname.go | 10 +++++-- pkg/transformers/prefixname_test.go | 4 +-- 8 files changed, 94 insertions(+), 18 deletions(-) diff --git a/pkg/app/application.go b/pkg/app/application.go index 16b31cbeb..146860626 100644 --- a/pkg/app/application.go +++ b/pkg/app/application.go @@ -292,7 +292,7 @@ func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error) } for _, v := range vars { id := resource.NewResId(v.ObjRef.GroupVersionKind(), v.ObjRef.Name) - if r, found := m[id]; found { + if r, found := m.DemandOneMatchForId(id); found { s, err := r.GetFieldValue(v.FieldRef.FieldPath) if err != nil { return nil, fmt.Errorf("failed to resolve referred var: %+v", v) diff --git a/pkg/app/application_test.go b/pkg/app/application_test.go index a425876f8..854dd1ac9 100644 --- a/pkg/app/application_test.go +++ b/pkg/app/application_test.go @@ -91,7 +91,7 @@ var svc = schema.GroupVersionKind{Version: "v1", Kind: "Service"} func TestResources1(t *testing.T) { expected := resmap.ResMap{ - resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap( + resource.NewResIdWithPrefix(deploy, "dply1", "foo-"): resource.NewResourceFromMap( map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", @@ -123,7 +123,7 @@ func TestResources1(t *testing.T) { }, }, }), - resource.NewResId(cmap, "literalConfigMap"): resource.NewResourceFromMap( + resource.NewResIdWithPrefix(cmap, "literalConfigMap", "foo-"): resource.NewResourceFromMap( map[string]interface{}{ "apiVersion": "v1", "kind": "ConfigMap", @@ -143,7 +143,7 @@ func TestResources1(t *testing.T) { "DB_PASSWORD": "somepw", }, }).SetBehavior(resource.BehaviorCreate), - resource.NewResId(secret, "secret"): resource.NewResourceFromMap( + resource.NewResIdWithPrefix(secret, "secret", "foo-"): resource.NewResourceFromMap( map[string]interface{}{ "apiVersion": "v1", "kind": "Secret", @@ -164,7 +164,7 @@ func TestResources1(t *testing.T) { "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), }, }).SetBehavior(resource.BehaviorCreate), - resource.NewResId(ns, "ns1"): resource.NewResourceFromMap( + resource.NewResIdWithPrefix(ns, "ns1", "foo-"): resource.NewResourceFromMap( map[string]interface{}{ "apiVersion": "v1", "kind": "Namespace", @@ -289,7 +289,7 @@ func makeLoader2(t *testing.T) loader.Loader { // perhaps it's not worth supporting the command. func TestRawResources2(t *testing.T) { expected := resmap.ResMap{ - resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap( + resource.NewResIdWithPrefix(deploy, "dply1", "foo-"): resource.NewResourceFromMap( map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", diff --git a/pkg/diff/rundiff.go b/pkg/diff/rundiff.go index 07c50d300..56aea4dac 100644 --- a/pkg/diff/rundiff.go +++ b/pkg/diff/rundiff.go @@ -56,7 +56,7 @@ func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) { } for id, obj := range in { - f, err := dir.newFile(id.String()) + f, err := dir.newFile(id.GvknString()) if err != nil { return nil, err } diff --git a/pkg/resmap/resmap.go b/pkg/resmap/resmap.go index 025bb8674..1329ed72e 100644 --- a/pkg/resmap/resmap.go +++ b/pkg/resmap/resmap.go @@ -37,6 +37,26 @@ import ( // ResMap is a map from ResId to Resource. type ResMap map[resource.ResId]*resource.Resource +// FindByGVKN find the matched ResIds by Group/Version/Kind and Name +func (m ResMap) FindByGVKN(inputId resource.ResId) []resource.ResId { + var result []resource.ResId + for id := range m { + if id.GvknEquals(inputId) { + result = append(result, id) + } + } + return result +} + +// DemandOneMatchForId find the matched resource by Group/Version/Kind and Name +func (m ResMap) DemandOneMatchForId(inputId resource.ResId) (*resource.Resource, bool) { + result := m.FindByGVKN(inputId) + if len(result) == 1 { + return m[result[0]], true + } + return nil, false +} + // EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`. func (m ResMap) EncodeAsYaml() ([]byte, error) { var ids []resource.ResId @@ -217,10 +237,12 @@ func MergeWithoutOverride(maps ...ResMap) (ResMap, error) { // must be BehaviorMerge or BehaviorReplace. If X is not in the map, then it's // behavior cannot be merge or replace. func MergeWithOverride(maps ...ResMap) (ResMap, error) { - result := ResMap{} - for _, m := range maps { + result := maps[0] + for _, m := range maps[1:] { for id, r := range m { - if _, found := result[id]; found { + matchedId := result.FindByGVKN(id) + if len(matchedId) == 1 { + id = matchedId[0] switch r.Behavior() { case resource.BehaviorReplace: glog.V(4).Infof("Replace %v with %v", result[id].Object, r.Object) @@ -236,13 +258,15 @@ func MergeWithOverride(maps ...ResMap) (ResMap, error) { default: return nil, fmt.Errorf("id %#v exists; must merge or replace", id) } - } else { + } else if len(matchedId) == 0 { switch r.Behavior() { case resource.BehaviorMerge, resource.BehaviorReplace: return nil, fmt.Errorf("id %#v does not exist; cannot merge or replace", id) default: result[id] = r } + } else { + return nil, fmt.Errorf("Merge conflict, found multiple objects %v the Resmap %v can merge into", matchedId, id) } } } diff --git a/pkg/resource/resid.go b/pkg/resource/resid.go index 38292056f..861875bf4 100644 --- a/pkg/resource/resid.go +++ b/pkg/resource/resid.go @@ -28,18 +28,49 @@ type ResId struct { gvk schema.GroupVersionKind // original name of the resource before transformation. name string + // namePrefix of the resource + // an untransformed resource has no prefix, fully transformed resource has an arbitrary number of prefixes + // concatenated together. + prefix string +} + +// NewResIdWithPrefix creates new resource identifier with a prefix +func NewResIdWithPrefix(g schema.GroupVersionKind, n, p string) ResId { + return ResId{gvk: g, name: n, prefix: p} } // NewResId creates new resource identifier func NewResId(g schema.GroupVersionKind, n string) ResId { - return ResId{gvk: g, name: n} + return NewResIdWithPrefix(g, n, "") } +// String of ResId based on GVK, name and prefix func (n ResId) String() string { + //var fields []string + //for _, s := range []string{n.gvk.Group, n.gvk.Version, n.gvk.Kind, n.prefix, n.name} { + // if s != "" { + // fields = append(fields, s) + // } + //} + //return strings.Join(fields, "_") + ".yaml" + fields := []string{n.gvk.Group, n.gvk.Version, n.gvk.Kind, n.prefix, n.name} + return strings.Join(fields, "_") + ".yaml" +} + +// GvknString of ResId based on GVK and name +func (n ResId) GvknString() string { if n.gvk.Group == "" { return strings.Join([]string{n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml" } return strings.Join([]string{n.gvk.Group, n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml" + +} + +// GvknEquals return if two ResId have the same Group/Version/Kind and name +// The comparison excludes prefix +func (n ResId) GvknEquals(id ResId) bool { + return n.gvk.Group == id.gvk.Group && n.gvk.Version == id.gvk.Version && + n.gvk.Kind == id.gvk.Kind && n.name == id.name } // Gvk returns Group/Version/Kind of the resource. @@ -51,3 +82,13 @@ func (n ResId) Gvk() schema.GroupVersionKind { func (n ResId) Name() string { return n.name } + +// Prefix returns name prefix. +func (n ResId) Prefix() string { + return n.prefix +} + +// CopyWithNewPrefix make a new copy from current ResId and append a new prefix +func (n ResId) CopyWithNewPrefix(p string) ResId { + return ResId{gvk: n.gvk, name: n.name, prefix: p + n.prefix} +} diff --git a/pkg/transformers/patch.go b/pkg/transformers/patch.go index f0b039007..638322c6e 100644 --- a/pkg/transformers/patch.go +++ b/pkg/transformers/patch.go @@ -55,10 +55,15 @@ func (pt *patchTransformer) Transform(baseResourceMap resmap.ResMap) error { for _, patch := range patches { // Merge patches with base resource. id := patch.Id() - base, found := baseResourceMap[id] - if !found { + matchedIds := baseResourceMap.FindByGVKN(id) + if len(matchedIds) == 0 { return fmt.Errorf("failed to find an object with %#v to apply the patch", id.Gvk()) } + if len(matchedIds) > 1 { + return fmt.Errorf("Found multiple objects %#v that the patch %#v can apply", matchedIds, id) + } + id = matchedIds[0] + base := baseResourceMap[id] merged := map[string]interface{}{} versionedObj, err := scheme.Scheme.New(id.Gvk()) baseName := base.GetName() diff --git a/pkg/transformers/prefixname.go b/pkg/transformers/prefixname.go index 1eb7d40e0..4070fa095 100644 --- a/pkg/transformers/prefixname.go +++ b/pkg/transformers/prefixname.go @@ -69,13 +69,17 @@ func (o *namePrefixTransformer) Transform(m resmap.ResMap) error { mf := resmap.ResMap{} for id := range m { - mf[id] = m[id] + found := false for _, path := range o.skipPathConfigs { if selectByGVK(id.Gvk(), path.GroupVersionKind) { - delete(mf, id) + found = true break } } + if !found { + mf[id] = m[id] + delete(m, id) + } } for id := range mf { @@ -88,6 +92,8 @@ func (o *namePrefixTransformer) Transform(m resmap.ResMap) error { if err != nil { return err } + newId := id.CopyWithNewPrefix(o.prefix) + m[newId] = mf[id] } } return nil diff --git a/pkg/transformers/prefixname_test.go b/pkg/transformers/prefixname_test.go index de95a246c..d1e03cfcf 100644 --- a/pkg/transformers/prefixname_test.go +++ b/pkg/transformers/prefixname_test.go @@ -53,7 +53,7 @@ func TestPrefixNameRun(t *testing.T) { }), } expected := resmap.ResMap{ - resource.NewResId(cmap, "cm1"): resource.NewResourceFromMap( + resource.NewResIdWithPrefix(cmap, "cm1", "someprefix-"): resource.NewResourceFromMap( map[string]interface{}{ "apiVersion": "v1", "kind": "ConfigMap", @@ -61,7 +61,7 @@ func TestPrefixNameRun(t *testing.T) { "name": "someprefix-cm1", }, }), - resource.NewResId(cmap, "cm2"): resource.NewResourceFromMap( + resource.NewResIdWithPrefix(cmap, "cm2", "someprefix-"): resource.NewResourceFromMap( map[string]interface{}{ "apiVersion": "v1", "kind": "ConfigMap",