diff --git a/cmd/init.go b/cmd/init.go index f75f6d87f3aad41409c05234c6272241edd3a168..61ca3cdd24d5866ce360d332ecf56eb8627703ac 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 7877e8ac56215daae30c567626ff17ce1a3f4fbc..35349e9826b8023c1c736d2f90b7007f5d104ff0 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 b7c62cf3d88342186217cb2e5526bce5c4bfdeeb..c2bd1adc6bc9e458c753a2570a4cd9cceac05954 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 c59056322820a68ddecd5cd806bf3d5310f76192..ae5da1aba618e714d3048e0da239a6ec7c57b6a3 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 62ef45db54872cddfc672dd64d747da35af05654..178106e635a26d0403c5bceeaa59e3109d83ddfd 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 fbd52d4df2e1408ee1bf9582ab24422990f8dbd8..1187d38e3d3f0c441f3ed69e9bb2a53220f4f761 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 }