From 6aa18fd947d90a328c75f8e577d06740b03d54f5 Mon Sep 17 00:00:00 2001 From: bryanl <bryanliles@gmail.com> Date: Wed, 21 Mar 2018 21:12:58 -0700 Subject: [PATCH] Allow `ks` command to work anywhere in app Allow ks command to work in any subdirectory of a ksonnet app. If `app.yaml` is not found in the current directory, look for it in parent directories. Signed-off-by: bryanl <bryanliles@gmail.com> --- cmd/root.go | 16 ++++------- metadata/app/app.go | 34 ++++++++++++++++++++++- metadata/app/app_test.go | 60 ++++++++++++++++++++++++++++++++++++++++ metadata/app/schema.go | 13 +++++++-- 4 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 metadata/app/app_test.go diff --git a/cmd/root.go b/cmd/root.go index 3401f641..eb508cd3 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 663742fc..775714e8 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 00000000..d89799f2 --- /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 12c4a36e..00405f79 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() -- GitLab