From c97498dfc9a328c2fbb0cbd478a57e39098429db Mon Sep 17 00:00:00 2001
From: Jessica Yuen <im.jessicayuen@gmail.com>
Date: Thu, 28 Sep 2017 10:32:36 -0700
Subject: [PATCH] Construct base components object

In order to support environment heirarchy, we need to be able to
reference all components. This commit will implement component imports
as k-v pairs ex: foo: "import/foo.jsonnet" as a Jsonnet object. This
jsonnet object will then be assigned as an ExtCode so that it can be
referenced by a base.libsonnet file which environments can build /
override upon.
---
 cmd/root.go          | 28 ++++++++++++++++-
 cmd/root_test.go     | 75 ++++++++++++++++++++++++++++++++++++++++++++
 template/expander.go | 16 ++++++++++
 3 files changed, 118 insertions(+), 1 deletion(-)
 create mode 100644 cmd/root_test.go

diff --git a/cmd/root.go b/cmd/root.go
index 8c4b074e..c429c6bb 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -22,6 +22,7 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"path"
 	"path/filepath"
 	"reflect"
 	"strings"
@@ -58,6 +59,8 @@ const (
 	// environment or the -f flag.
 	flagFile      = "file"
 	flagFileShort = "f"
+
+	componentsExtCodeKey = "__ksonnet/components"
 )
 
 var clientConfig clientcmd.ClientConfig
@@ -369,8 +372,9 @@ func expandEnvCmdObjs(cmd *cobra.Command, envSpec *envSpec, cwd metadata.AbsPath
 			if err != nil {
 				return nil, err
 			}
+			baseObjExtCode := fmt.Sprintf("%s=%s", componentsExtCodeKey, constructBaseObj(fileNames))
+			expander.ExtCodes = append([]string{baseObjExtCode})
 		}
-
 	}
 
 	//
@@ -379,3 +383,25 @@ func expandEnvCmdObjs(cmd *cobra.Command, envSpec *envSpec, cwd metadata.AbsPath
 
 	return expander.Expand(fileNames)
 }
+
+// constructBaseObj constructs the base Jsonnet object that represents k-v
+// pairs of component name -> component imports. For example,
+//
+//   {
+//      foo: import "components/foo.jsonnet"
+//   }
+func constructBaseObj(paths []string) string {
+	var obj bytes.Buffer
+	obj.WriteString("{\n")
+	for _, p := range paths {
+		ext := path.Ext(p)
+		if path.Ext(p) != ".jsonnet" {
+			continue
+		}
+
+		name := strings.TrimSuffix(path.Base(p), ext)
+		fmt.Fprintf(&obj, "  %s: import \"%s\",\n", name, p)
+	}
+	obj.WriteString("}\n")
+	return obj.String()
+}
diff --git a/cmd/root_test.go b/cmd/root_test.go
new file mode 100644
index 00000000..3ee755b6
--- /dev/null
+++ b/cmd/root_test.go
@@ -0,0 +1,75 @@
+// Copyright 2017 The kubecfg authors
+//
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+package cmd
+
+import (
+	"testing"
+)
+
+func TestConstructBaseObj(t *testing.T) {
+	tests := []struct {
+		inputPaths []string
+		expected   string
+	}{
+		// test simple case with 1 .jsonnet path
+		{
+			[]string{
+				"some/fake/path/foo.jsonnet",
+			},
+			`{
+  foo: import "some/fake/path/foo.jsonnet",
+}
+`,
+		},
+		// test multiple .jsonnet path case
+		{
+			[]string{
+				"some/fake/path/foo.jsonnet",
+				"another/fake/path/bar.jsonnet",
+			},
+			`{
+  foo: import "some/fake/path/foo.jsonnet",
+  bar: import "another/fake/path/bar.jsonnet",
+}
+`,
+		},
+		// test zero path case
+		{
+			[]string{},
+			`{
+}
+`,
+		},
+		// test non-jsonnet extension case
+		{
+			[]string{
+				"some/fake/path/foo.libsonnet",
+				"another/fake/path/bar.jsonnet",
+			},
+			`{
+  bar: import "another/fake/path/bar.jsonnet",
+}
+`,
+		},
+	}
+
+	for _, s := range tests {
+		res := constructBaseObj(s.inputPaths)
+		if res != s.expected {
+			t.Errorf("Wrong object constructed\n  expected: %v\n  got: %v", s.expected, res)
+		}
+	}
+}
diff --git a/template/expander.go b/template/expander.go
index d414db8c..4c7fbe00 100644
--- a/template/expander.go
+++ b/template/expander.go
@@ -20,6 +20,7 @@ type Expander struct {
 	ExtVarFiles []string
 	TlaVars     []string
 	TlaVarFiles []string
+	ExtCodes    []string
 
 	Resolver   string
 	FailAction string
@@ -112,6 +113,21 @@ func (spec *Expander) jsonnetVM() (*jsonnet.VM, error) {
 		vm.TlaVar(kv[0], string(v))
 	}
 
+	for _, extcode := range spec.ExtCodes {
+		kv := strings.SplitN(extcode, "=", 2)
+		switch len(kv) {
+		case 1:
+			v, present := os.LookupEnv(kv[0])
+			if present {
+				vm.ExtCode(kv[0], v)
+			} else {
+				return nil, fmt.Errorf("Missing environment variable: %s", kv[0])
+			}
+		case 2:
+			vm.ExtCode(kv[0], kv[1])
+		}
+	}
+
 	resolver, err := spec.buildResolver()
 	if err != nil {
 		return nil, err
-- 
GitLab