From 6faf4675acc068e30fc6c9d5a0872bd43a7a72ef Mon Sep 17 00:00:00 2001
From: Matt Butcher <technosophos@gmail.com>
Date: Thu, 15 Dec 2016 17:06:30 -0700
Subject: [PATCH] fix(tller): allow deep merge of global maps

This reverts a previous decision to only do shallow merges of globals.
It allows globals to be nested maps.
---
 pkg/chartutil/values.go      | 34 ++++++++++++++++++++++++++++------
 pkg/chartutil/values_test.go | 11 +++++++++++
 2 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go
index c431454a7..9d6b3d218 100644
--- a/pkg/chartutil/values.go
+++ b/pkg/chartutil/values.go
@@ -216,15 +216,30 @@ func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} {
 		return dg
 	}
 
-	// We manually copy (instead of using coalesceTables) because (a) we need
-	// to prevent loops, and (b) we disallow nesting tables under globals.
-	// Globals should _just_ be k/v pairs.
+	// EXPERIMENTAL: In the past, we have disallowed globals to test tables. This
+	// reverses that decision. It may somehow be possible to introduce a loop
+	// here, but I haven't found a way. So for the time being, let's allow
+	// tables in globals.
 	for key, val := range sg {
 		if istable(val) {
-			log.Printf("warning: nested values are illegal in globals (%s)", key)
-			continue
+			vv := copyMap(val.(map[string]interface{}))
+			if destv, ok := dg[key]; ok {
+				if destvmap, ok := destv.(map[string]interface{}); ok {
+					// Basically, we reverse order of coalesce here to merge
+					// top-down.
+					coalesceTables(vv, destvmap)
+					dg[key] = vv
+					continue
+				} else {
+					log.Printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key)
+				}
+			} else {
+				// Here there is no merge. We're just adding.
+				dg[key] = vv
+			}
 		} else if dv, ok := dg[key]; ok && istable(dv) {
-			log.Printf("warning: nested values are illegal in globals (%s)", key)
+			// It's not clear if this condition can actually ever trigger.
+			log.Printf("key %s is table. Skipping", key)
 			continue
 		}
 		// TODO: Do we need to do any additional checking on the value?
@@ -232,7 +247,14 @@ func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} {
 	}
 	dest[GlobalKey] = dg
 	return dest
+}
 
+func copyMap(src map[string]interface{}) map[string]interface{} {
+	dest := make(map[string]interface{}, len(src))
+	for k, v := range src {
+		dest[k] = v
+	}
+	return dest
 }
 
 // coalesceValues builds up a values map for a particular chart.
diff --git a/pkg/chartutil/values_test.go b/pkg/chartutil/values_test.go
index b48d4e943..3816d9dea 100644
--- a/pkg/chartutil/values_test.go
+++ b/pkg/chartutil/values_test.go
@@ -251,11 +251,16 @@ top: yup
 global:
   name: Ishmael
   subject: Queequeg
+  nested:
+    boat: true
 
 pequod:
   global:
     name: Stinky
     harpooner: Tashtego
+    nested:
+      boat: false
+      sail: true
   ahab:
     scope: whale
 `
@@ -295,6 +300,12 @@ func TestCoalesceValues(t *testing.T) {
 		{"{{.pequod.global.subject}}", "Queequeg"},
 		{"{{.spouter.global.name}}", "Ishmael"},
 		{"{{.spouter.global.harpooner}}", "<no value>"},
+
+		{"{{.global.nested.boat}}", "true"},
+		{"{{.pequod.global.nested.boat}}", "true"},
+		{"{{.spouter.global.nested.boat}}", "true"},
+		{"{{.pequod.global.nested.sail}}", "true"},
+		{"{{.spouter.global.nested.sail}}", "<no value>"},
 	}
 
 	for _, tt := range tests {
-- 
GitLab