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