Commit 3ff6fb1d authored by Derek Wilson's avatar Derek Wilson
Browse files

Issue #756 make --dir global persistent flag



includes refactor moving app creation to later in the initialization
process and only when needed. also fixes regression preventing commands
that don't need an app from running outside an app directory.
Signed-off-by: default avatarDerek Wilson <derek@heptio.com>
parent 004404df
...@@ -31,6 +31,8 @@ import ( ...@@ -31,6 +31,8 @@ import (
const ( const (
// OptionApp is app option. // OptionApp is app option.
OptionApp = "app" OptionApp = "app"
// OptionAppRoot is the root directory of the application.
OptionAppRoot = "app-root"
// OptionArguments is arguments option. Used for passing arguments to prototypes. // OptionArguments is arguments option. Used for passing arguments to prototypes.
OptionArguments = "arguments" OptionArguments = "arguments"
// OptionAsString is asString. Used for setting values as strings. // OptionAsString is asString. Used for setting values as strings.
...@@ -67,6 +69,8 @@ const ( ...@@ -67,6 +69,8 @@ const (
OptionGlobal = "global" OptionGlobal = "global"
// OptionGracePeriod is gracePeriod option. // OptionGracePeriod is gracePeriod option.
OptionGracePeriod = "grace-period" OptionGracePeriod = "grace-period"
// OptionHTTPClient is the http.Client for outbound network requests.
OptionHTTPClient = "http-client"
// OptionInstalled is for listing installed packages. // OptionInstalled is for listing installed packages.
OptionInstalled = "only-installed" OptionInstalled = "only-installed"
// OptionJPaths is jsonnet paths. // OptionJPaths is jsonnet paths.
...@@ -79,6 +83,8 @@ const ( ...@@ -79,6 +83,8 @@ const (
OptionModule = "module" OptionModule = "module"
// OptionNamespace is a cluster namespace option // OptionNamespace is a cluster namespace option
OptionNamespace = "namespace" OptionNamespace = "namespace"
// OptionNewRoot is init new root path option.
OptionNewRoot = "root-path"
// OptionNewEnvName is newEnvName option. Used for renaming environments. // OptionNewEnvName is newEnvName option. Used for renaming environments.
OptionNewEnvName = "new-env-name" OptionNewEnvName = "new-env-name"
// OptionOutput is output option. // OptionOutput is output option.
...@@ -94,12 +100,12 @@ const ( ...@@ -94,12 +100,12 @@ const (
// OptionResolveImage is resolve image option. It is used to resolve docker image references // OptionResolveImage is resolve image option. It is used to resolve docker image references
// when setting parameters. // when setting parameters.
OptionResolveImage = "resolve-image" OptionResolveImage = "resolve-image"
// OptionRootPath is path option.
OptionRootPath = "root-path"
// OptionServer is server option. // OptionServer is server option.
OptionServer = "server" OptionServer = "server"
// OptionServerURI is serverURI option. // OptionServerURI is serverURI option.
OptionServerURI = "server-uri" OptionServerURI = "server-uri"
// OptionSkipCheckUpgrade tells app not to emit upgrade warnings, probably because the user is already upgrading.
OptionSkipCheckUpgrade = "skip-check-upgrade"
// OptionSkipDefaultRegistries is skipDefaultRegistries option. Used by init. // OptionSkipDefaultRegistries is skipDefaultRegistries option. Used by init.
OptionSkipDefaultRegistries = "skip-default-registries" OptionSkipDefaultRegistries = "skip-default-registries"
// OptionSkipGc is skipGc option. // OptionSkipGc is skipGc option.
...@@ -185,15 +191,15 @@ func newOptionLoader(m map[string]interface{}) *optionLoader { ...@@ -185,15 +191,15 @@ func newOptionLoader(m map[string]interface{}) *optionLoader {
} }
} }
func (o *optionLoader) LoadFs(name string) afero.Fs { func (o *optionLoader) LoadFs() afero.Fs {
i := o.load(name) i := o.loadOptional(OptionFs)
if i == nil { if i == nil {
return nil return afero.NewOsFs()
} }
a, ok := i.(afero.Fs) a, ok := i.(afero.Fs)
if !ok { if !ok {
o.err = newInvalidOptionError(name) o.err = newInvalidOptionError(OptionFs)
return nil return nil
} }
...@@ -332,24 +338,63 @@ func (o *optionLoader) LoadClientConfig() *client.Config { ...@@ -332,24 +338,63 @@ func (o *optionLoader) LoadClientConfig() *client.Config {
return a return a
} }
// LoadApp returns an app.App reference - either as passed via OptionApp,
// or newly constructed.
func (o *optionLoader) LoadApp() app.App { func (o *optionLoader) LoadApp() app.App {
i := o.load(OptionApp) i := o.loadOptional(OptionApp)
if i == nil { a, ok := i.(app.App)
o.err = ErrNotInApp if i != nil && !ok {
// App was provided but was invalid type
o.err = newInvalidOptionError(OptionApp)
return nil return nil
} }
if a != nil {
// Return app if a valid app.App was provided
return a
}
a, ok := i.(app.App) var fs = o.LoadFs()
if !ok { if fs == nil {
o.err = newInvalidOptionError(OptionApp) o.err = errors.New("missing required fs reference")
return nil
}
var httpClient = o.LoadHTTPClient()
if httpClient == nil {
o.err = errors.New("initializing http client")
return nil
}
var appRoot = o.LoadOptionalString(OptionAppRoot)
appRoot, err := app.FindRoot(fs, appRoot)
if err != nil {
o.err = errors.Wrapf(err, "finding app root from starting path: %s", appRoot)
return nil return nil
} }
a, err = app.Load(fs, httpClient, appRoot)
if err != nil {
o.err = errors.New("initializing app")
return nil
}
if !o.LoadOptionalBool(OptionSkipCheckUpgrade) {
if _, err := a.CheckUpgrade(); err != nil {
o.err = errors.Wrap(err, "checking for app upgrades")
return nil
}
}
return a return a
} }
// LoadHTTPClient loads an HTTP client based on common configuration for certificates, tls verification, timeouts, etc. // LoadHTTPClient loads an HTTP client based on common configuration for certificates, tls verification, timeouts, etc.
func (o *optionLoader) LoadHTTPClient() *http.Client { func (o *optionLoader) LoadHTTPClient() *http.Client {
i := o.loadOptional(OptionHTTPClient)
if c, ok := i.(*http.Client); ok {
return c
}
// Construct a client if none was passed
tlsSkipVerify := o.LoadOptionalBool(OptionTLSSkipVerify) tlsSkipVerify := o.LoadOptionalBool(OptionTLSSkipVerify)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
......
...@@ -55,7 +55,6 @@ func Test_optionLoader_types(t *testing.T) { ...@@ -55,7 +55,6 @@ func Test_optionLoader_types(t *testing.T) {
}, },
{ {
name: "Fs", name: "Fs",
hasArg: true,
valid: afero.NewMemMapFs(), valid: afero.NewMemMapFs(),
invalid: "invalid", invalid: "invalid",
keyName: OptionFs, keyName: OptionFs,
......
...@@ -40,7 +40,7 @@ func RunInit(m map[string]interface{}) error { ...@@ -40,7 +40,7 @@ func RunInit(m map[string]interface{}) error {
return i.Run() return i.Run()
} }
type appLoadFn func(fs afero.Fs, httpClient *http.Client, root string, skipFindRoot bool) (app.App, error) type appLoadFn func(fs afero.Fs, httpClient *http.Client, root string) (app.App, error)
type appInitFn func(fs afero.Fs, httpClient *http.Client, name, rootPath, envName, k8sSpecFlag, serverURI, namespace string, registries []registry.Registry) error type appInitFn func(fs afero.Fs, httpClient *http.Client, name, rootPath, envName, k8sSpecFlag, serverURI, namespace string, registries []registry.Registry) error
...@@ -69,9 +69,9 @@ func NewInit(m map[string]interface{}) (*Init, error) { ...@@ -69,9 +69,9 @@ func NewInit(m map[string]interface{}) (*Init, error) {
ol := newOptionLoader(m) ol := newOptionLoader(m)
i := &Init{ i := &Init{
fs: ol.LoadFs(OptionFs), fs: ol.LoadFs(),
name: ol.LoadString(OptionName), name: ol.LoadString(OptionName),
rootPath: ol.LoadString(OptionRootPath), rootPath: ol.LoadString(OptionNewRoot),
envName: ol.LoadString(OptionEnvName), envName: ol.LoadString(OptionEnvName),
k8sSpecFlag: ol.LoadString(OptionSpecFlag), k8sSpecFlag: ol.LoadString(OptionSpecFlag),
serverURI: ol.LoadOptionalString(OptionServer), serverURI: ol.LoadOptionalString(OptionServer),
...@@ -97,7 +97,7 @@ func (i *Init) Run() error { ...@@ -97,7 +97,7 @@ func (i *Init) Run() error {
var registries []registry.Registry var registries []registry.Registry
if !i.skipDefaultRegistries { if !i.skipDefaultRegistries {
a, err := i.appLoadFn(i.fs, i.httpClient, i.rootPath, true) a, err := i.appLoadFn(i.fs, i.httpClient, i.rootPath)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -60,7 +60,7 @@ func TestInit(t *testing.T) { ...@@ -60,7 +60,7 @@ func TestInit(t *testing.T) {
in := map[string]interface{}{ in := map[string]interface{}{
OptionFs: aFs, OptionFs: aFs,
OptionName: aName, OptionName: aName,
OptionRootPath: aRootPath, OptionNewRoot: aRootPath,
OptionEnvName: tc.envName, OptionEnvName: tc.envName,
OptionSpecFlag: aK8sSpecFlag, OptionSpecFlag: aK8sSpecFlag,
OptionServer: aServerURI, OptionServer: aServerURI,
...@@ -95,7 +95,7 @@ func TestInit(t *testing.T) { ...@@ -95,7 +95,7 @@ func TestInit(t *testing.T) {
return nil return nil
} }
a.appLoadFn = func(fs afero.Fs, httpClient *http.Client, root string, skipFindRoot bool) (app.App, error) { a.appLoadFn = func(fs afero.Fs, httpClient *http.Client, root string) (app.App, error) {
return appMock, nil return appMock, nil
} }
......
...@@ -103,16 +103,8 @@ type App interface { ...@@ -103,16 +103,8 @@ type App interface {
} }
// Load loads the application configuration. // Load loads the application configuration.
func Load(fs afero.Fs, httpClient *http.Client, cwd string, skipFindRoot bool) (App, error) { func Load(fs afero.Fs, httpClient *http.Client, appRoot string) (App, error) {
log := log.WithField("action", "app.Load") log := log.WithField("action", "app.Load")
appRoot := cwd
if !skipFindRoot {
var err error
appRoot, err = findRoot(fs, cwd)
if err != nil {
return nil, err
}
}
spec, err := read(fs, appRoot) spec, err := read(fs, appRoot)
if os.IsNotExist(err) { if os.IsNotExist(err) {
...@@ -232,7 +224,7 @@ func cleanEnv(fs afero.Fs, root string) error { ...@@ -232,7 +224,7 @@ func cleanEnv(fs afero.Fs, root string) error {
return nil return nil
} }
func findRoot(fs afero.Fs, cwd string) (string, error) { func FindRoot(fs afero.Fs, cwd string) (string, error) {
prev := cwd prev := cwd
for { for {
......
...@@ -22,7 +22,7 @@ import ( ...@@ -22,7 +22,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func Test_findRoot(t *testing.T) { func Test_FindRoot(t *testing.T) {
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
stageFile(t, fs, "app010_app.yaml", "/app/app.yaml") stageFile(t, fs, "app010_app.yaml", "/app/app.yaml")
...@@ -61,7 +61,7 @@ func Test_findRoot(t *testing.T) { ...@@ -61,7 +61,7 @@ func Test_findRoot(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
root, err := findRoot(fs, tc.name) root, err := FindRoot(fs, tc.name)
if tc.isErr { if tc.isErr {
require.Error(t, err) require.Error(t, err)
return return
......
...@@ -75,7 +75,7 @@ func (i *initApp) Run() error { ...@@ -75,7 +75,7 @@ func (i *initApp) Run() error {
} }
// Load application. // Load application.
a, err := app.Load(i.fs, i.httpClient, i.rootPath, false) a, err := app.Load(i.fs, i.httpClient, i.rootPath)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -18,6 +18,7 @@ package clicmd ...@@ -18,6 +18,7 @@ package clicmd
import ( import (
"github.com/ksonnet/ksonnet/pkg/actions" "github.com/ksonnet/ksonnet/pkg/actions"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/viper"
) )
type initName int type initName int
...@@ -116,3 +117,8 @@ func runAction(name initName, args map[string]interface{}) error { ...@@ -116,3 +117,8 @@ func runAction(name initName, args map[string]interface{}) error {
return fn(args) return fn(args)
} }
func addGlobalOptions(m map[string]interface{}) {
m[actions.OptionTLSSkipVerify] = viper.GetBool(flagTLSSkipVerify)
m[actions.OptionAppRoot] = viper.GetString(flagDir)
}
...@@ -17,9 +17,9 @@ package clicmd ...@@ -17,9 +17,9 @@ package clicmd
import ( import (
"github.com/ksonnet/ksonnet/pkg/actions" "github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/ksonnet/ksonnet/pkg/client" "github.com/ksonnet/ksonnet/pkg/client"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/afero"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
...@@ -82,8 +82,8 @@ ks apply dev -c guestbook-ui -c nginx-depl --create false ...@@ -82,8 +82,8 @@ ks apply dev -c guestbook-ui -c nginx-depl --create false
` `
) )
func newApplyCmd(a app.App) *cobra.Command { func newApplyCmd(fs afero.Fs) *cobra.Command {
applyClientConfig := client.NewDefaultClientConfig(a) applyClientConfig := client.NewDefaultClientConfig()
applyCmd := &cobra.Command{ applyCmd := &cobra.Command{
Use: "apply <env-name> [-c <component-name>] [--dry-run]", Use: "apply <env-name> [-c <component-name>] [--dry-run]",
...@@ -95,7 +95,6 @@ func newApplyCmd(a app.App) *cobra.Command { ...@@ -95,7 +95,6 @@ func newApplyCmd(a app.App) *cobra.Command {
} }
m := map[string]interface{}{ m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionClientConfig: applyClientConfig, actions.OptionClientConfig: applyClientConfig,
actions.OptionComponentNames: viper.GetStringSlice(vApplyComponent), actions.OptionComponentNames: viper.GetStringSlice(vApplyComponent),
actions.OptionCreate: viper.GetBool(vApplyCreate), actions.OptionCreate: viper.GetBool(vApplyCreate),
...@@ -104,8 +103,9 @@ func newApplyCmd(a app.App) *cobra.Command { ...@@ -104,8 +103,9 @@ func newApplyCmd(a app.App) *cobra.Command {
actions.OptionGcTag: viper.GetString(vApplyGcTag), actions.OptionGcTag: viper.GetString(vApplyGcTag),
actions.OptionSkipGc: viper.GetBool(vApplySkipGc), actions.OptionSkipGc: viper.GetBool(vApplySkipGc),
} }
addGlobalOptions(m)
if err := extractJsonnetFlags(a, "apply"); err != nil { if err := extractJsonnetFlags(fs, "apply"); err != nil {
return errors.Wrap(err, "handle jsonnet flags") return errors.Wrap(err, "handle jsonnet flags")
} }
......
...@@ -19,11 +19,10 @@ import ( ...@@ -19,11 +19,10 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newComponentCmd(a app.App) *cobra.Command { func newComponentCmd() *cobra.Command {
componentCmd := &cobra.Command{ componentCmd := &cobra.Command{
Use: "component", Use: "component",
Short: "Manage ksonnet components", Short: "Manage ksonnet components",
...@@ -35,8 +34,8 @@ func newComponentCmd(a app.App) *cobra.Command { ...@@ -35,8 +34,8 @@ func newComponentCmd(a app.App) *cobra.Command {
}, },
} }
componentCmd.AddCommand(newComponentListCmd(a)) componentCmd.AddCommand(newComponentListCmd())
componentCmd.AddCommand(newComponentRmCmd(a)) componentCmd.AddCommand(newComponentRmCmd())
return componentCmd return componentCmd
......
...@@ -19,7 +19,6 @@ import ( ...@@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"github.com/ksonnet/ksonnet/pkg/actions" "github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
...@@ -38,7 +37,7 @@ The ` + "`list`" + ` command displays all known components. ...@@ -38,7 +37,7 @@ The ` + "`list`" + ` command displays all known components.
ks component list` ks component list`
) )
func newComponentListCmd(a app.App) *cobra.Command { func newComponentListCmd() *cobra.Command {
componentListCmd := &cobra.Command{ componentListCmd := &cobra.Command{
Use: "list", Use: "list",
Short: "List known components", Short: "List known components",
...@@ -50,10 +49,10 @@ func newComponentListCmd(a app.App) *cobra.Command { ...@@ -50,10 +49,10 @@ func newComponentListCmd(a app.App) *cobra.Command {
} }
m := map[string]interface{}{ m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionModule: viper.GetString(vComponentListNamespace), actions.OptionModule: viper.GetString(vComponentListNamespace),
actions.OptionOutput: viper.GetString(vComponentListOutput), actions.OptionOutput: viper.GetString(vComponentListOutput),
} }
addGlobalOptions(m)
return runAction(actionComponentList, m) return runAction(actionComponentList, m)
}, },
......
...@@ -19,7 +19,6 @@ import ( ...@@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"github.com/ksonnet/ksonnet/pkg/actions" "github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
...@@ -33,7 +32,7 @@ references throughout the project.` ...@@ -33,7 +32,7 @@ references throughout the project.`
ks component rm guestbook` ks component rm guestbook`
) )
func newComponentRmCmd(a app.App) *cobra.Command { func newComponentRmCmd() *cobra.Command {
componentRmCmd := &cobra.Command{ componentRmCmd := &cobra.Command{
Use: "rm <component-name>", Use: "rm <component-name>",
Short: "Delete a component from the ksonnet application", Short: "Delete a component from the ksonnet application",
...@@ -45,9 +44,9 @@ func newComponentRmCmd(a app.App) *cobra.Command { ...@@ -45,9 +44,9 @@ func newComponentRmCmd(a app.App) *cobra.Command {
} }
m := map[string]interface{}{ m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionComponentName: args[0], actions.OptionComponentName: args[0],
} }
addGlobalOptions(m)
return runAction(actionComponentRm, m) return runAction(actionComponentRm, m)
}, },
......
...@@ -17,9 +17,9 @@ package clicmd ...@@ -17,9 +17,9 @@ package clicmd
import ( import (
"github.com/ksonnet/ksonnet/pkg/actions" "github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/ksonnet/ksonnet/pkg/client" "github.com/ksonnet/ksonnet/pkg/client"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/afero"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
...@@ -57,8 +57,8 @@ ks delete dev ...@@ -57,8 +57,8 @@ ks delete dev
ks delete --kubeconfig=./kubeconfig -c nginx` ks delete --kubeconfig=./kubeconfig -c nginx`
) )
func newDeleteCmd(a app.App) *cobra.Command { func newDeleteCmd(fs afero.Fs) *cobra.Command {
deleteClientConfig := client.NewDefaultClientConfig(a) deleteClientConfig := client.NewDefaultClientConfig()
deleteCmd := &cobra.Command{ deleteCmd := &cobra.Command{
Use: "delete [env-name] [-c <component-name>]", Use: "delete [env-name] [-c <component-name>]",
...@@ -72,14 +72,14 @@ func newDeleteCmd(a app.App) *cobra.Command { ...@@ -72,14 +72,14 @@ func newDeleteCmd(a app.App) *cobra.Command {
} }
m := map[string]interface{}{ m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionClientConfig: deleteClientConfig, actions.OptionClientConfig: deleteClientConfig,
actions.OptionComponentNames: viper.GetStringSlice(vDeleteComponent), actions.OptionComponentNames: viper.GetStringSlice(vDeleteComponent),
actions.OptionEnvName: envName, actions.OptionEnvName: envName,
actions.OptionGracePeriod: viper.GetInt64(vDeleteGracePeriod), actions.OptionGracePeriod: viper.GetInt64(vDeleteGracePeriod),
} }
addGlobalOptions(m)
if err := extractJsonnetFlags(a, "delete"); err != nil { if err := extractJsonnetFlags(fs, "delete"); err != nil {
return errors.Wrap(err, "handle jsonnet flags") return errors.Wrap(err, "handle jsonnet flags")
} }
......
...@@ -19,11 +19,11 @@ import ( ...@@ -19,11 +19,11 @@ import (
"fmt" "fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/afero"
"github.com/spf13/cobra"