Commit 6aa18fd9 authored by bryanl's avatar bryanl
Browse files

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: default avatarbryanl <bryanliles@gmail.com>
parent c4ee18ba
......@@ -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
......
......@@ -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
}
}
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)
})
}
}
......@@ -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()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment