From 98c7428f1221bf36bd83fb9d6a48dce1d8c7f695 Mon Sep 17 00:00:00 2001
From: Alex Clemmer <clemmer.alexander@gmail.com>
Date: Sat, 9 Sep 2017 22:31:19 -0700
Subject: [PATCH] Emit `k.libsonnet` during `init` subcommand

This commit fixes #113, causing us to emit `k.libsonnet` (a collection
of extensions to the k8s API) by updating the dependency on
`ksonnet-lib/ksonnet-gen` and exercising the new signature for
`ksonnet.Emit`, which handles this automatically.

We update the `vendor/` directory and the code that depends on it in the
same commit so that we don't break bisect.
---
 metadata/manager.go                           | 27 +++---
 metadata/manager_test.go                      | 20 +++--
 .../ksonnet-lib/ksonnet-gen/ksonnet/emit.go   | 11 ++-
 .../ksonnet-gen/kubeversion/data.go           | 84 +++++++++++++++++++
 .../ksonnet-gen/kubeversion/version.go        | 12 +++
 vendor/vendor.json                            | 20 ++---
 6 files changed, 146 insertions(+), 28 deletions(-)

diff --git a/metadata/manager.go b/metadata/manager.go
index b0809b4d..a89624e9 100644
--- a/metadata/manager.go
+++ b/metadata/manager.go
@@ -27,9 +27,10 @@ const (
 	defaultEnvName = "default"
 
 	// Environment-specific files
-	schemaFilename         = "swagger.json"
-	ksonnetLibCoreFilename = "k8s.libsonnet"
-	specFilename           = "spec.json"
+	schemaFilename        = "swagger.json"
+	extensionsLibFilename = "k.libsonnet"
+	k8sLibFilename        = "k8s.libsonnet"
+	specFilename          = "spec.json"
 )
 
 type manager struct {
@@ -82,7 +83,7 @@ func initManager(rootPath AbsPath, spec ClusterSpec, appFS afero.Fs) (*manager,
 	// result in a partially-initialized directory structure.
 	//
 	ksonnetLibDir := appendToAbsPath(m.environmentsDir, defaultEnvName)
-	ksonnetLibData, err := generateKsonnetLibData(ksonnetLibDir, specData)
+	extensionsLibData, k8sLibData, err := generateKsonnetLibData(ksonnetLibDir, specData)
 	if err != nil {
 		return nil, err
 	}
@@ -93,7 +94,7 @@ func initManager(rootPath AbsPath, spec ClusterSpec, appFS afero.Fs) (*manager,
 	}
 
 	// Cache specification data.
-	if err = m.createEnvironment(defaultEnvName, specData, ksonnetLibData); err != nil {
+	if err = m.createEnvironment(defaultEnvName, specData, extensionsLibData, k8sLibData); err != nil {
 		return nil, err
 	}
 
@@ -140,7 +141,7 @@ func (m *manager) LibPaths(envName string) (libPath, envLibPath AbsPath) {
 	return m.libPath, appendToAbsPath(m.environmentsDir, envName)
 }
 
-func (m *manager) createEnvironment(name string, specData, ksonnetLibData []byte) error {
+func (m *manager) createEnvironment(name string, specData, extensionsLibData, k8sLibData []byte) error {
 	envPath := appendToAbsPath(m.environmentsDir, name)
 	err := m.appFS.MkdirAll(string(envPath), os.ModePerm)
 	if err != nil {
@@ -154,8 +155,14 @@ func (m *manager) createEnvironment(name string, specData, ksonnetLibData []byte
 		return err
 	}
 
-	ksonnetLibPath := appendToAbsPath(envPath, ksonnetLibCoreFilename)
-	err = afero.WriteFile(m.appFS, string(ksonnetLibPath), ksonnetLibData, 0644)
+	k8sLibPath := appendToAbsPath(envPath, k8sLibFilename)
+	err = afero.WriteFile(m.appFS, string(k8sLibPath), k8sLibData, 0644)
+	if err != nil {
+		return err
+	}
+
+	extensionsLibPath := appendToAbsPath(envPath, extensionsLibFilename)
+	err = afero.WriteFile(m.appFS, string(extensionsLibPath), extensionsLibData, 0644)
 	return err
 }
 
@@ -184,12 +191,12 @@ func (m *manager) createAppDirTree() error {
 	return nil
 }
 
-func generateKsonnetLibData(ksonnetLibDir AbsPath, text []byte) ([]byte, error) {
+func generateKsonnetLibData(ksonnetLibDir AbsPath, text []byte) ([]byte, []byte, error) {
 	// Deserialize the API object.
 	s := kubespec.APISpec{}
 	err := json.Unmarshal(text, &s)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	s.Text = text
diff --git a/metadata/manager_test.go b/metadata/manager_test.go
index d88ed7e4..af011875 100644
--- a/metadata/manager_test.go
+++ b/metadata/manager_test.go
@@ -22,7 +22,7 @@ const (
   "definitions": {
   }
 }`
-	blankKsonnetLib = `// AUTOGENERATED from the Kubernetes OpenAPI specification. DO NOT MODIFY.
+	blankK8sLib = `// AUTOGENERATED from the Kubernetes OpenAPI specification. DO NOT MODIFY.
 // Kubernetes version: v1.7.0
 
 {
@@ -79,12 +79,20 @@ func TestInitSuccess(t *testing.T) {
 		t.Fatalf("Expected swagger file at '%s' to have value: '%s', got: '%s'", schemaPath, blankSwaggerData, actualSwagger)
 	}
 
-	ksonnetLibPath := appendToAbsPath(envPath, ksonnetLibCoreFilename)
-	ksonnetLibBytes, err := afero.ReadFile(testFS, string(ksonnetLibPath))
+	k8sLibPath := appendToAbsPath(envPath, k8sLibFilename)
+	k8sLibBytes, err := afero.ReadFile(testFS, string(k8sLibPath))
 	if err != nil {
-		t.Fatalf("Failed to read ksonnet-lib file at '%s':\n%v", ksonnetLibPath, err)
-	} else if actualKsonnetLib := string(ksonnetLibBytes); actualKsonnetLib != blankKsonnetLib {
-		t.Fatalf("Expected swagger file at '%s' to have value: '%s', got: '%s'", ksonnetLibPath, blankKsonnetLib, actualKsonnetLib)
+		t.Fatalf("Failed to read ksonnet-lib file at '%s':\n%v", k8sLibPath, err)
+	} else if actualK8sLib := string(k8sLibBytes); actualK8sLib != blankK8sLib {
+		t.Fatalf("Expected swagger file at '%s' to have value: '%s', got: '%s'", k8sLibPath, blankK8sLib, actualK8sLib)
+	}
+
+	extensionsLibPath := appendToAbsPath(envPath, extensionsLibFilename)
+	extensionsLibBytes, err := afero.ReadFile(testFS, string(extensionsLibPath))
+	if err != nil {
+		t.Fatalf("Failed to read ksonnet-lib file at '%s':\n%v", extensionsLibPath, err)
+	} else if string(extensionsLibBytes) == "" {
+		t.Fatalf("Expected extension library file at '%s' to be non-empty", extensionsLibPath)
 	}
 }
 
diff --git a/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/ksonnet/emit.go b/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/ksonnet/emit.go
index 908ff43c..2b0b98c3 100644
--- a/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/ksonnet/emit.go
+++ b/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/ksonnet/emit.go
@@ -13,12 +13,19 @@ import (
 
 // Emit takes a swagger API specification, and returns the text of
 // `ksonnet-lib`, written in Jsonnet.
-func Emit(spec *kubespec.APISpec, ksonnetLibSHA, k8sSHA *string) ([]byte, error) {
+func Emit(spec *kubespec.APISpec, ksonnetLibSHA, k8sSHA *string) ([]byte, []byte, error) {
 	root := newRoot(spec, ksonnetLibSHA, k8sSHA)
 
 	m := newIndentWriter()
 	root.emit(m)
-	return m.bytes()
+	k8sBytes, err := m.bytes()
+	if err != nil {
+		return nil, nil, err
+	}
+
+	kBytes := []byte(kubeversion.KSource(spec.Info.Version))
+
+	return kBytes, k8sBytes, nil
 }
 
 //-----------------------------------------------------------------------------
diff --git a/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubeversion/data.go b/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubeversion/data.go
index a455bebc..bd90e066 100644
--- a/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubeversion/data.go
+++ b/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubeversion/data.go
@@ -67,6 +67,9 @@ var versions = map[string]versionData{
 			"APIResourceList":                  "apiResourceList",
 			"APIVersions":                      "apiVersions",
 			"ServerAddressByClientCIDR":        "serverAddressByClientCidr",
+
+			// Collisions with Jsonnet keywords.
+			"local": "localStorage",
 		},
 		constructorSpecs: map[string][]CustomConstructorSpec{
 			"io.k8s.kubernetes.pkg.api.v1.Container": []CustomConstructorSpec{
@@ -250,5 +253,86 @@ var versions = map[string]versionData{
 			// Misc.
 			"io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetSpec": newPropertySet("templateGeneration"),
 		},
+		kSource: `local k8s = import "k8s.libsonnet";
+
+local apps = k8s.apps;
+local core = k8s.core;
+local extensions = k8s.extensions;
+
+local hidden = {
+  mapContainers(f):: {
+    local podContainers = super.spec.template.spec.containers,
+    spec+: {
+      template+: {
+        spec+: {
+          // IMPORTANT: This overwrites the 'containers' field
+          // for this deployment.
+          containers: std.map(f, podContainers),
+        },
+      },
+    },
+  },
+
+  mapContainersWithName(names, f) ::
+    local nameSet =
+      if std.type(names) == "array"
+      then std.set(names)
+      else std.set([names]);
+    local inNameSet(name) = std.length(std.setInter(nameSet, std.set([name]))) > 0;
+    self.mapContainers(
+      function(c)
+        if std.objectHas(c, "name") && inNameSet(c.name)
+        then f(c)
+        else c
+    ),
+};
+
+k8s + {
+  apps:: apps + {
+    v1beta1:: apps.v1beta1 + {
+      local v1beta1 = apps.v1beta1,
+
+      daemonSet:: v1beta1.daemonSet + {
+        mapContainers(f):: hidden.mapContainers(f),
+        mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
+      },
+
+      deployment:: v1beta1.deployment + {
+        mapContainers(f):: hidden.mapContainers(f),
+        mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
+      },
+    },
+  },
+
+  core:: core + {
+    v1:: core.v1 + {
+      list:: {
+        new(items)::
+          {apiVersion: "v1"} +
+          {kind: "List"} +
+          self.items(items),
+
+        items(items):: if std.type(items) == "array" then {items+: items} else {items+: [items]},
+      },
+    },
+  },
+
+  extensions:: extensions + {
+    v1beta1:: extensions.v1beta1 + {
+      local v1beta1 = extensions.v1beta1,
+
+      daemonSet:: v1beta1.daemonSet + {
+        mapContainers(f):: hidden.mapContainers(f),
+        mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
+      },
+
+      deployment:: v1beta1.deployment + {
+        mapContainers(f):: hidden.mapContainers(f),
+        mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
+      },
+    },
+  },
+}
+`,
 	},
 }
diff --git a/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubeversion/version.go b/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubeversion/version.go
index f988fcfe..39467dee 100644
--- a/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubeversion/version.go
+++ b/vendor/github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubeversion/version.go
@@ -15,6 +15,17 @@ import (
 	"github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubespec"
 )
 
+// KSource returns the source of `k.libsonnet` for a specific version
+// of Kubernetes.
+func KSource(k8sVersion string) string {
+	verData, ok := versions[k8sVersion]
+	if !ok {
+		log.Fatalf("Unrecognized Kubernetes version '%s'", k8sVersion)
+	}
+
+	return verData.kSource
+}
+
 // MapIdentifier takes a text identifier and maps it to a
 // Jsonnet-appropriate identifier, for some version of Kubernetes. For
 // example, in Kubernetes v1.7.0, we might map `clusterIP` ->
@@ -75,6 +86,7 @@ type versionData struct {
 	idAliases         map[string]string
 	constructorSpecs  map[string][]CustomConstructorSpec
 	propertyBlacklist map[string]propertySet
+	kSource           string
 }
 
 type propertySet map[string]bool
diff --git a/vendor/vendor.json b/vendor/vendor.json
index a626c95f..54f12a54 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -211,26 +211,26 @@
 		{
 			"checksumSHA1": "wX+GmcWpMzCIcxR9YtN1FCM9BEE=",
 			"path": "github.com/ksonnet/ksonnet-lib/ksonnet-gen/jsonnet",
-			"revision": "fcf4c31408afc7b7469a1722c4cc06ccc315a722",
-			"revisionTime": "2017-08-16T22:14:57Z"
+			"revision": "b27d2d7778c12c3d762d24c1b9fd4c41da6c73c7",
+			"revisionTime": "2017-09-10T05:18:39Z"
 		},
 		{
-			"checksumSHA1": "LGk0N301rp2uQSOmy7kBQEksues=",
+			"checksumSHA1": "zJx/j+IS6qd0P5h2m8WEQEtrACU=",
 			"path": "github.com/ksonnet/ksonnet-lib/ksonnet-gen/ksonnet",
-			"revision": "fcf4c31408afc7b7469a1722c4cc06ccc315a722",
-			"revisionTime": "2017-08-16T22:14:57Z"
+			"revision": "b27d2d7778c12c3d762d24c1b9fd4c41da6c73c7",
+			"revisionTime": "2017-09-10T05:18:39Z"
 		},
 		{
 			"checksumSHA1": "BiiHRiYpSOb+vHiP6h/A9lRotEY=",
 			"path": "github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubespec",
-			"revision": "fcf4c31408afc7b7469a1722c4cc06ccc315a722",
-			"revisionTime": "2017-08-16T22:14:57Z"
+			"revision": "b27d2d7778c12c3d762d24c1b9fd4c41da6c73c7",
+			"revisionTime": "2017-09-10T05:18:39Z"
 		},
 		{
-			"checksumSHA1": "jpBOSqq1T9UyiANimAKtzyTt3qo=",
+			"checksumSHA1": "lr+Bx7/N2KHk9dOrsWfLYBPjbdQ=",
 			"path": "github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubeversion",
-			"revision": "fcf4c31408afc7b7469a1722c4cc06ccc315a722",
-			"revisionTime": "2017-08-16T22:14:57Z"
+			"revision": "b27d2d7778c12c3d762d24c1b9fd4c41da6c73c7",
+			"revisionTime": "2017-09-10T05:18:39Z"
 		},
 		{
 			"checksumSHA1": "T8soMJArSZrYnhmdpAnq1bVxQ6Q=",
-- 
GitLab