diff --git a/cmd/root.go b/cmd/root.go index 3401f6414ae31b056ae17d4b1b1c7b916e526d77..eb508cd344f15687470dd5298a7a3c8b943703ae 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -116,18 +116,14 @@ application configuration to remote clusters. return err } - appConfig := filepath.Join(wd, "app.yaml") - exists, err := afero.Exists(appFs, appConfig) - if err != nil { - return err + var isInit bool + if len(args) == 2 && args[0] == "init" { + isInit = true } - if exists { - log.Debugf("loading configuration from %s", appConfig) - ka, err = app.Load(appFs, wd) - if err != nil { - return err - } + ka, err = app.Load(appFs, wd) + if err != nil && isInit { + return err } return nil diff --git a/metadata/app/app.go b/metadata/app/app.go index 663742fc44e26722f33fb587d2e46851b6b655d5..775714e805ea764df298a59d522e1e47e4cbb20d 100644 --- a/metadata/app/app.go +++ b/metadata/app/app.go @@ -129,7 +129,12 @@ func (ba *baseApp) EnvironmentParams(envName string) (string, error) { } // Load loads the application configuration. -func Load(fs afero.Fs, appRoot string) (App, error) { +func Load(fs afero.Fs, cwd string) (App, error) { + appRoot, err := findRoot(fs, cwd) + if err != nil { + return nil, err + } + spec, err := Read(fs, appRoot) if err != nil { return nil, err @@ -261,3 +266,30 @@ func cleanEnv(fs afero.Fs, root string) error { return nil } + +func findRoot(fs afero.Fs, cwd string) (string, error) { + prev := cwd + + for { + path := filepath.Join(cwd, appYamlName) + exists, err := afero.Exists(fs, path) + if err != nil { + return "", err + } + + if exists { + return cwd, nil + } + + cwd, err = filepath.Abs(filepath.Join(cwd, "..")) + if err != nil { + return "", err + } + + if cwd == prev { + return "", errors.Errorf("unable to find ksonnet project") + } + + prev = cwd + } +} diff --git a/metadata/app/app_test.go b/metadata/app/app_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d89799f256969267e0433b9b5b1915ca3ddefbe1 --- /dev/null +++ b/metadata/app/app_test.go @@ -0,0 +1,60 @@ +package app + +import ( + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +func Test_findRoot(t *testing.T) { + fs := afero.NewMemMapFs() + stageFile(t, fs, "app010_app.yaml", "/app/app.yaml") + + dirs := []string{ + "/app/components", + "/invalid", + } + + for _, dir := range dirs { + err := fs.MkdirAll(dir, DefaultFilePermissions) + require.NoError(t, err) + } + + cases := []struct { + name string + expected string + isErr bool + }{ + { + name: "/app", + expected: "/app", + }, + { + name: "/app/components", + expected: "/app", + }, + { + name: "/invalid", + isErr: true, + }, + { + name: "/missing", + isErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + root, err := findRoot(fs, tc.name) + if tc.isErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tc.expected, root) + }) + } + +} diff --git a/metadata/app/schema.go b/metadata/app/schema.go index 12c4a36ea9cd20bc58007f478a999cab929828a1..00405f79a89eb4d96fdd9543bc6b03fcf0cda7b5 100644 --- a/metadata/app/schema.go +++ b/metadata/app/schema.go @@ -22,6 +22,7 @@ import ( "github.com/blang/semver" "github.com/ghodss/yaml" "github.com/pkg/errors" + log "github.com/sirupsen/logrus" "github.com/spf13/afero" ) @@ -66,9 +67,13 @@ type Spec struct { License string `json:"license,omitempty"` } -// Read will return the specification for a ksonnet application. -func Read(fs afero.Fs, appRoot string) (*Spec, error) { - bytes, err := afero.ReadFile(fs, specPath(appRoot)) +// Read will return the specification for a ksonnet application. It will navigate up directories +// to search for `app.yaml` and return error if it hits the root directory. +func Read(fs afero.Fs, root string) (*Spec, error) { + config := filepath.Join(root, appYamlName) + log.Debugf("loading configuration from %s", config) + + bytes, err := afero.ReadFile(fs, config) if err != nil { return nil, err } @@ -97,6 +102,8 @@ func Read(fs afero.Fs, appRoot string) (*Spec, error) { return schema, nil } + + // Write writes the provided spec to file system. func Write(fs afero.Fs, appRoot string, spec *Spec) error { data, err := spec.Marshal()