Add support for using common base

This commit is contained in:
Jingfang Liu
2018-07-26 14:02:13 -07:00
parent 4569a09d54
commit 58bc4b14a2
8 changed files with 94 additions and 18 deletions

View File

@@ -292,7 +292,7 @@ func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error)
} }
for _, v := range vars { for _, v := range vars {
id := resource.NewResId(v.ObjRef.GroupVersionKind(), v.ObjRef.Name) 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) s, err := r.GetFieldValue(v.FieldRef.FieldPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to resolve referred var: %+v", v) return nil, fmt.Errorf("failed to resolve referred var: %+v", v)

View File

@@ -91,7 +91,7 @@ var svc = schema.GroupVersionKind{Version: "v1", Kind: "Service"}
func TestResources1(t *testing.T) { func TestResources1(t *testing.T) {
expected := resmap.ResMap{ expected := resmap.ResMap{
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap( resource.NewResIdWithPrefix(deploy, "dply1", "foo-"): resource.NewResourceFromMap(
map[string]interface{}{ map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "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{}{ map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -143,7 +143,7 @@ func TestResources1(t *testing.T) {
"DB_PASSWORD": "somepw", "DB_PASSWORD": "somepw",
}, },
}).SetBehavior(resource.BehaviorCreate), }).SetBehavior(resource.BehaviorCreate),
resource.NewResId(secret, "secret"): resource.NewResourceFromMap( resource.NewResIdWithPrefix(secret, "secret", "foo-"): resource.NewResourceFromMap(
map[string]interface{}{ map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
@@ -164,7 +164,7 @@ func TestResources1(t *testing.T) {
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
}, },
}).SetBehavior(resource.BehaviorCreate), }).SetBehavior(resource.BehaviorCreate),
resource.NewResId(ns, "ns1"): resource.NewResourceFromMap( resource.NewResIdWithPrefix(ns, "ns1", "foo-"): resource.NewResourceFromMap(
map[string]interface{}{ map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Namespace", "kind": "Namespace",
@@ -289,7 +289,7 @@ func makeLoader2(t *testing.T) loader.Loader {
// perhaps it's not worth supporting the command. // perhaps it's not worth supporting the command.
func TestRawResources2(t *testing.T) { func TestRawResources2(t *testing.T) {
expected := resmap.ResMap{ expected := resmap.ResMap{
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap( resource.NewResIdWithPrefix(deploy, "dply1", "foo-"): resource.NewResourceFromMap(
map[string]interface{}{ map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",

View File

@@ -56,7 +56,7 @@ func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
} }
for id, obj := range in { for id, obj := range in {
f, err := dir.newFile(id.String()) f, err := dir.newFile(id.GvknString())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -37,6 +37,26 @@ import (
// ResMap is a map from ResId to Resource. // ResMap is a map from ResId to Resource.
type ResMap map[resource.ResId]*resource.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 `---`. // EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`.
func (m ResMap) EncodeAsYaml() ([]byte, error) { func (m ResMap) EncodeAsYaml() ([]byte, error) {
var ids []resource.ResId 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 // must be BehaviorMerge or BehaviorReplace. If X is not in the map, then it's
// behavior cannot be merge or replace. // behavior cannot be merge or replace.
func MergeWithOverride(maps ...ResMap) (ResMap, error) { func MergeWithOverride(maps ...ResMap) (ResMap, error) {
result := ResMap{} result := maps[0]
for _, m := range maps { for _, m := range maps[1:] {
for id, r := range m { for id, r := range m {
if _, found := result[id]; found { matchedId := result.FindByGVKN(id)
if len(matchedId) == 1 {
id = matchedId[0]
switch r.Behavior() { switch r.Behavior() {
case resource.BehaviorReplace: case resource.BehaviorReplace:
glog.V(4).Infof("Replace %v with %v", result[id].Object, r.Object) glog.V(4).Infof("Replace %v with %v", result[id].Object, r.Object)
@@ -236,13 +258,15 @@ func MergeWithOverride(maps ...ResMap) (ResMap, error) {
default: default:
return nil, fmt.Errorf("id %#v exists; must merge or replace", id) return nil, fmt.Errorf("id %#v exists; must merge or replace", id)
} }
} else { } else if len(matchedId) == 0 {
switch r.Behavior() { switch r.Behavior() {
case resource.BehaviorMerge, resource.BehaviorReplace: case resource.BehaviorMerge, resource.BehaviorReplace:
return nil, fmt.Errorf("id %#v does not exist; cannot merge or replace", id) return nil, fmt.Errorf("id %#v does not exist; cannot merge or replace", id)
default: default:
result[id] = r result[id] = r
} }
} else {
return nil, fmt.Errorf("Merge conflict, found multiple objects %v the Resmap %v can merge into", matchedId, id)
} }
} }
} }

View File

@@ -28,18 +28,49 @@ type ResId struct {
gvk schema.GroupVersionKind gvk schema.GroupVersionKind
// original name of the resource before transformation. // original name of the resource before transformation.
name string 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 // NewResId creates new resource identifier
func NewResId(g schema.GroupVersionKind, n string) ResId { 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 { 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 == "" { if n.gvk.Group == "" {
return strings.Join([]string{n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml" 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" 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. // Gvk returns Group/Version/Kind of the resource.
@@ -51,3 +82,13 @@ func (n ResId) Gvk() schema.GroupVersionKind {
func (n ResId) Name() string { func (n ResId) Name() string {
return n.name 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}
}

View File

@@ -55,10 +55,15 @@ func (pt *patchTransformer) Transform(baseResourceMap resmap.ResMap) error {
for _, patch := range patches { for _, patch := range patches {
// Merge patches with base resource. // Merge patches with base resource.
id := patch.Id() id := patch.Id()
base, found := baseResourceMap[id] matchedIds := baseResourceMap.FindByGVKN(id)
if !found { if len(matchedIds) == 0 {
return fmt.Errorf("failed to find an object with %#v to apply the patch", id.Gvk()) 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{}{} merged := map[string]interface{}{}
versionedObj, err := scheme.Scheme.New(id.Gvk()) versionedObj, err := scheme.Scheme.New(id.Gvk())
baseName := base.GetName() baseName := base.GetName()

View File

@@ -69,13 +69,17 @@ func (o *namePrefixTransformer) Transform(m resmap.ResMap) error {
mf := resmap.ResMap{} mf := resmap.ResMap{}
for id := range m { for id := range m {
mf[id] = m[id] found := false
for _, path := range o.skipPathConfigs { for _, path := range o.skipPathConfigs {
if selectByGVK(id.Gvk(), path.GroupVersionKind) { if selectByGVK(id.Gvk(), path.GroupVersionKind) {
delete(mf, id) found = true
break break
} }
} }
if !found {
mf[id] = m[id]
delete(m, id)
}
} }
for id := range mf { for id := range mf {
@@ -88,6 +92,8 @@ func (o *namePrefixTransformer) Transform(m resmap.ResMap) error {
if err != nil { if err != nil {
return err return err
} }
newId := id.CopyWithNewPrefix(o.prefix)
m[newId] = mf[id]
} }
} }
return nil return nil

View File

@@ -53,7 +53,7 @@ func TestPrefixNameRun(t *testing.T) {
}), }),
} }
expected := resmap.ResMap{ expected := resmap.ResMap{
resource.NewResId(cmap, "cm1"): resource.NewResourceFromMap( resource.NewResIdWithPrefix(cmap, "cm1", "someprefix-"): resource.NewResourceFromMap(
map[string]interface{}{ map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -61,7 +61,7 @@ func TestPrefixNameRun(t *testing.T) {
"name": "someprefix-cm1", "name": "someprefix-cm1",
}, },
}), }),
resource.NewResId(cmap, "cm2"): resource.NewResourceFromMap( resource.NewResIdWithPrefix(cmap, "cm2", "someprefix-"): resource.NewResourceFromMap(
map[string]interface{}{ map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",