From 4dd0dd7a2c0b65ffbcd78b35738b09e825ee5ab2 Mon Sep 17 00:00:00 2001
From: Alex Clemmer <clemmer.alexander@gmail.com>
Date: Mon, 30 Oct 2017 13:54:59 -0700
Subject: [PATCH] Emit `app.yaml` after init

When the user calls `ks init <whatever>`, we need to emit an `app.yaml`
for the new project. This commit will introduce such behavior.
---
 cmd/init.go                  |  4 ++--
 metadata/environment_test.go |  2 +-
 metadata/interface.go        |  4 ++--
 metadata/manager.go          | 37 ++++++++++++++++++++++++++++++------
 metadata/manager_test.go     | 18 +++++++++++++-----
 pkg/kubecfg/init.go          |  7 ++++---
 6 files changed, 53 insertions(+), 19 deletions(-)

diff --git a/cmd/init.go b/cmd/init.go
index f75f6d87..61ca3cdd 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -70,7 +70,7 @@ var initCmd = &cobra.Command{
 			return err
 		}
 
-		c, err := kubecfg.NewInitCmd(appRoot, specFlag, &uri, &namespace)
+		c, err := kubecfg.NewInitCmd(appName, appRoot, specFlag, &uri, &namespace)
 		if err != nil {
 			return err
 		}
@@ -112,7 +112,7 @@ consists of two steps:
   # Initialize ksonnet application, using an OpenAPI specification file
   # generated by a build of Kubernetes to generate 'ksonnet-lib'.
   ks init app-name --api-spec=file:swagger.json
-  
+
   # Initialize ksonnet application, using the context 'dev' from the kubeconfig
   # file.
   ks init app-name --context=dev`,
diff --git a/metadata/environment_test.go b/metadata/environment_test.go
index 7877e8ac..35349e98 100644
--- a/metadata/environment_test.go
+++ b/metadata/environment_test.go
@@ -46,7 +46,7 @@ func mockEnvironments(t *testing.T, appName string) *manager {
 	}
 
 	appPath := AbsPath(appName)
-	m, err := initManager(appPath, spec, &mockAPIServer, &mockNamespace, testFS)
+	m, err := initManager(appName, appPath, spec, &mockAPIServer, &mockNamespace, testFS)
 	if err != nil {
 		t.Fatalf("Failed to init cluster spec: %v", err)
 	}
diff --git a/metadata/interface.go b/metadata/interface.go
index b7c62cf3..c2bd1adc 100644
--- a/metadata/interface.go
+++ b/metadata/interface.go
@@ -78,8 +78,8 @@ func Find(path AbsPath) (Manager, error) {
 // Init will retrieve a cluster API specification, generate a
 // capabilities-compliant version of ksonnet-lib, and then generate the
 // directory tree for an application.
-func Init(rootPath AbsPath, spec ClusterSpec, serverURI, namespace *string) (Manager, error) {
-	return initManager(rootPath, spec, serverURI, namespace, appFS)
+func Init(name string, rootPath AbsPath, spec ClusterSpec, serverURI, namespace *string) (Manager, error) {
+	return initManager(name, rootPath, spec, serverURI, namespace, appFS)
 }
 
 // ClusterSpec represents the API supported by some cluster. There are several
diff --git a/metadata/manager.go b/metadata/manager.go
index c5905632..ae5da1ab 100644
--- a/metadata/manager.go
+++ b/metadata/manager.go
@@ -16,6 +16,7 @@
 package metadata
 
 import (
+	"encoding/json"
 	"fmt"
 	"os"
 	"os/user"
@@ -23,6 +24,7 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/ksonnet/ksonnet/metadata/app"
 	param "github.com/ksonnet/ksonnet/metadata/params"
 	"github.com/ksonnet/ksonnet/prototype"
 	log "github.com/sirupsen/logrus"
@@ -43,6 +45,7 @@ const (
 
 	componentParamsFile = "params.libsonnet"
 	baseLibsonnetFile   = "base.libsonnet"
+	appYAMLFile         = "app.yaml"
 
 	// ComponentsExtCodeKey is the ExtCode key for component imports
 	ComponentsExtCodeKey = "__ksonnet/components"
@@ -63,10 +66,11 @@ type manager struct {
 	libPath          AbsPath
 	componentsPath   AbsPath
 	environmentsPath AbsPath
-	vendorDir        AbsPath
+	vendorPath       AbsPath
 
 	componentParamsPath AbsPath
 	baseLibsonnetPath   AbsPath
+	appYAMLPath         AbsPath
 
 	// User-level paths.
 	userKsonnetRootPath AbsPath
@@ -95,7 +99,7 @@ func findManager(abs AbsPath, appFS afero.Fs) (*manager, error) {
 	}
 }
 
-func initManager(rootPath AbsPath, spec ClusterSpec, serverURI, namespace *string, appFS afero.Fs) (*manager, error) {
+func initManager(name string, rootPath AbsPath, spec ClusterSpec, serverURI, namespace *string, appFS afero.Fs) (*manager, error) {
 	m, err := newManager(rootPath, appFS)
 	if err != nil {
 		return nil, err
@@ -114,7 +118,7 @@ func initManager(rootPath AbsPath, spec ClusterSpec, serverURI, namespace *strin
 	}
 
 	// Initialize directory structure.
-	if err := m.createAppDirTree(); err != nil {
+	if err := m.createAppDirTree(name); err != nil {
 		return nil, err
 	}
 
@@ -150,10 +154,11 @@ func newManager(rootPath AbsPath, appFS afero.Fs) (*manager, error) {
 		libPath:          appendToAbsPath(rootPath, libDir),
 		componentsPath:   appendToAbsPath(rootPath, componentsDir),
 		environmentsPath: appendToAbsPath(rootPath, environmentsDir),
-		vendorDir:        appendToAbsPath(rootPath, vendorDir),
+		vendorPath:       appendToAbsPath(rootPath, vendorDir),
 
 		componentParamsPath: appendToAbsPath(rootPath, componentsDir, componentParamsFile),
 		baseLibsonnetPath:   appendToAbsPath(rootPath, environmentsDir, baseLibsonnetFile),
+		appYAMLPath:         appendToAbsPath(rootPath, appYAMLFile),
 
 		// User-level paths.
 		userKsonnetRootPath: userRootPath,
@@ -270,7 +275,7 @@ func (m *manager) createUserDirTree() error {
 	return nil
 }
 
-func (m *manager) createAppDirTree() error {
+func (m *manager) createAppDirTree(name string) error {
 	exists, err := afero.DirExists(m.appFS, string(m.rootPath))
 	if err != nil {
 		return fmt.Errorf("Could not check existance of directory '%s':\n%v", m.rootPath, err)
@@ -284,7 +289,7 @@ func (m *manager) createAppDirTree() error {
 		m.libPath,
 		m.componentsPath,
 		m.environmentsPath,
-		m.vendorDir,
+		m.vendorPath,
 	}
 
 	for _, p := range dirPaths {
@@ -293,6 +298,11 @@ func (m *manager) createAppDirTree() error {
 		}
 	}
 
+	appYAML, err := genAppYAMLContent(name)
+	if err != nil {
+		return err
+	}
+
 	filePaths := []struct {
 		path    AbsPath
 		content []byte
@@ -305,6 +315,10 @@ func (m *manager) createAppDirTree() error {
 			m.baseLibsonnetPath,
 			genBaseLibsonnetContent(),
 		},
+		{
+			m.appYAMLPath,
+			appYAML,
+		},
 	}
 
 	for _, f := range filePaths {
@@ -344,6 +358,17 @@ func genComponentParamsContent() []byte {
 `)
 }
 
+func genAppYAMLContent(name string) ([]byte, error) {
+	content := app.Spec{
+		APIVersion: app.DefaultAPIVersion,
+		Kind:       app.Kind,
+		Name:       name,
+		Version:    app.DefaultVersion,
+	}
+
+	return json.MarshalIndent(&content, "", "  ")
+}
+
 func genBaseLibsonnetContent() []byte {
 	return []byte(`local components = std.extVar("` + ComponentsExtCodeKey + `");
 components + {
diff --git a/metadata/manager_test.go b/metadata/manager_test.go
index 62ef45db..178106e6 100644
--- a/metadata/manager_test.go
+++ b/metadata/manager_test.go
@@ -61,7 +61,7 @@ func TestInitSuccess(t *testing.T) {
 	}
 
 	appPath := AbsPath("/fromEmptySwagger")
-	_, err = initManager(appPath, spec, &mockAPIServer, &mockNamespace, testFS)
+	_, err = initManager("fromEmptySwagger", appPath, spec, &mockAPIServer, &mockNamespace, testFS)
 	if err != nil {
 		t.Fatalf("Failed to init cluster spec: %v", err)
 	}
@@ -150,6 +150,14 @@ func TestInitSuccess(t *testing.T) {
 	} else if len(baseLibsonnetBytes) == 0 {
 		t.Fatalf("Expected base.libsonnet at '%s' to be non-empty", baseLibsonnetPath)
 	}
+
+	appYAMLPath := appendToAbsPath(appPath, appYAMLFile)
+	appYAMLBytes, err := afero.ReadFile(testFS, string(appYAMLPath))
+	if err != nil {
+		t.Fatalf("Failed to read app.yaml file at '%s':\n%v", appYAMLPath, err)
+	} else if len(appYAMLBytes) == 0 {
+		t.Fatalf("Expected app.yaml at '%s' to be non-empty", appYAMLPath)
+	}
 }
 
 func TestFindSuccess(t *testing.T) {
@@ -168,7 +176,7 @@ func TestFindSuccess(t *testing.T) {
 	}
 
 	appPath := AbsPath("/findSuccess")
-	_, err = initManager(appPath, spec, &mockAPIServer, &mockNamespace, testFS)
+	_, err = initManager("findSuccess", appPath, spec, &mockAPIServer, &mockNamespace, testFS)
 	if err != nil {
 		t.Fatalf("Failed to init cluster spec: %v", err)
 	}
@@ -196,7 +204,7 @@ func TestComponentPaths(t *testing.T) {
 	}
 
 	appPath := AbsPath("/componentPaths")
-	m, err := initManager(appPath, spec, &mockAPIServer, &mockNamespace, testFS)
+	m, err := initManager("componentPaths", appPath, spec, &mockAPIServer, &mockNamespace, testFS)
 	if err != nil {
 		t.Fatalf("Failed to init cluster spec: %v", err)
 	}
@@ -286,13 +294,13 @@ func TestDoubleNewFailure(t *testing.T) {
 
 	appPath := AbsPath("/doubleNew")
 
-	_, err = initManager(appPath, spec, &mockAPIServer, &mockNamespace, testFS)
+	_, err = initManager("doubleNew", appPath, spec, &mockAPIServer, &mockNamespace, testFS)
 	if err != nil {
 		t.Fatalf("Failed to init cluster spec: %v", err)
 	}
 
 	targetErr := fmt.Sprintf("Could not create app; directory '%s' already exists", appPath)
-	_, err = initManager(appPath, spec, &mockAPIServer, &mockNamespace, testFS)
+	_, err = initManager("doubleNew", appPath, spec, &mockAPIServer, &mockNamespace, testFS)
 	if err == nil || err.Error() != targetErr {
 		t.Fatalf("Expected to fail to create app with message '%s', got '%s'", targetErr, err.Error())
 	}
diff --git a/pkg/kubecfg/init.go b/pkg/kubecfg/init.go
index fbd52d4d..1187d38e 100644
--- a/pkg/kubecfg/init.go
+++ b/pkg/kubecfg/init.go
@@ -3,13 +3,14 @@ package kubecfg
 import "github.com/ksonnet/ksonnet/metadata"
 
 type InitCmd struct {
+	name      string
 	rootPath  metadata.AbsPath
 	spec      metadata.ClusterSpec
 	serverURI *string
 	namespace *string
 }
 
-func NewInitCmd(rootPath metadata.AbsPath, specFlag string, serverURI, namespace *string) (*InitCmd, error) {
+func NewInitCmd(name string, rootPath metadata.AbsPath, specFlag string, serverURI, namespace *string) (*InitCmd, error) {
 	// NOTE: We're taking `rootPath` here as an absolute path (rather than a partial path we expand to an absolute path)
 	// to make it more testable.
 
@@ -18,10 +19,10 @@ func NewInitCmd(rootPath metadata.AbsPath, specFlag string, serverURI, namespace
 		return nil, err
 	}
 
-	return &InitCmd{rootPath: rootPath, spec: spec, serverURI: serverURI, namespace: namespace}, nil
+	return &InitCmd{name: name, rootPath: rootPath, spec: spec, serverURI: serverURI, namespace: namespace}, nil
 }
 
 func (c *InitCmd) Run() error {
-	_, err := metadata.Init(c.rootPath, c.spec, c.serverURI, c.namespace)
+	_, err := metadata.Init(c.name, c.rootPath, c.spec, c.serverURI, c.namespace)
 	return err
 }
-- 
GitLab