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 (
const (
// OptionApp is app option.
OptionApp = "app"
// OptionAppRoot is the root directory of the application.
OptionAppRoot = "app-root"
// OptionArguments is arguments option. Used for passing arguments to prototypes.
OptionArguments = "arguments"
// OptionAsString is asString. Used for setting values as strings.
......@@ -67,6 +69,8 @@ const (
OptionGlobal = "global"
// OptionGracePeriod is gracePeriod option.
OptionGracePeriod = "grace-period"
// OptionHTTPClient is the http.Client for outbound network requests.
OptionHTTPClient = "http-client"
// OptionInstalled is for listing installed packages.
OptionInstalled = "only-installed"
// OptionJPaths is jsonnet paths.
......@@ -79,6 +83,8 @@ const (
OptionModule = "module"
// OptionNamespace is a cluster namespace option
OptionNamespace = "namespace"
// OptionNewRoot is init new root path option.
OptionNewRoot = "root-path"
// OptionNewEnvName is newEnvName option. Used for renaming environments.
OptionNewEnvName = "new-env-name"
// OptionOutput is output option.
......@@ -94,12 +100,12 @@ const (
// OptionResolveImage is resolve image option. It is used to resolve docker image references
// when setting parameters.
OptionResolveImage = "resolve-image"
// OptionRootPath is path option.
OptionRootPath = "root-path"
// OptionServer is server option.
OptionServer = "server"
// OptionServerURI is serverURI option.
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 = "skip-default-registries"
// OptionSkipGc is skipGc option.
......@@ -185,15 +191,15 @@ func newOptionLoader(m map[string]interface{}) *optionLoader {
}
}
func (o *optionLoader) LoadFs(name string) afero.Fs {
i := o.load(name)
func (o *optionLoader) LoadFs() afero.Fs {
i := o.loadOptional(OptionFs)
if i == nil {
return nil
return afero.NewOsFs()
}
a, ok := i.(afero.Fs)
if !ok {
o.err = newInvalidOptionError(name)
o.err = newInvalidOptionError(OptionFs)
return nil
}
......@@ -332,24 +338,63 @@ func (o *optionLoader) LoadClientConfig() *client.Config {
return a
}
// LoadApp returns an app.App reference - either as passed via OptionApp,
// or newly constructed.
func (o *optionLoader) LoadApp() app.App {
i := o.load(OptionApp)
if i == nil {
o.err = ErrNotInApp
i := o.loadOptional(OptionApp)
a, ok := i.(app.App)
if i != nil && !ok {
// App was provided but was invalid type
o.err = newInvalidOptionError(OptionApp)
return nil
}
if a != nil {
// Return app if a valid app.App was provided
return a
}
a, ok := i.(app.App)
if !ok {
o.err = newInvalidOptionError(OptionApp)
var fs = o.LoadFs()
if fs == nil {
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
}
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
}
// LoadHTTPClient loads an HTTP client based on common configuration for certificates, tls verification, timeouts, etc.
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)
tlsConfig := &tls.Config{
......
......@@ -55,7 +55,6 @@ func Test_optionLoader_types(t *testing.T) {
},
{
name: "Fs",
hasArg: true,
valid: afero.NewMemMapFs(),
invalid: "invalid",
keyName: OptionFs,
......
......@@ -40,7 +40,7 @@ func RunInit(m map[string]interface{}) error {
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
......@@ -69,9 +69,9 @@ func NewInit(m map[string]interface{}) (*Init, error) {
ol := newOptionLoader(m)
i := &Init{
fs: ol.LoadFs(OptionFs),
fs: ol.LoadFs(),
name: ol.LoadString(OptionName),
rootPath: ol.LoadString(OptionRootPath),
rootPath: ol.LoadString(OptionNewRoot),
envName: ol.LoadString(OptionEnvName),
k8sSpecFlag: ol.LoadString(OptionSpecFlag),
serverURI: ol.LoadOptionalString(OptionServer),
......@@ -97,7 +97,7 @@ func (i *Init) Run() error {
var registries []registry.Registry
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 {
return err
}
......
......@@ -60,7 +60,7 @@ func TestInit(t *testing.T) {
in := map[string]interface{}{
OptionFs: aFs,
OptionName: aName,
OptionRootPath: aRootPath,
OptionNewRoot: aRootPath,
OptionEnvName: tc.envName,
OptionSpecFlag: aK8sSpecFlag,
OptionServer: aServerURI,
......@@ -95,7 +95,7 @@ func TestInit(t *testing.T) {
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
}
......
......@@ -103,16 +103,8 @@ type App interface {
}
// 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")
appRoot := cwd
if !skipFindRoot {
var err error
appRoot, err = findRoot(fs, cwd)
if err != nil {
return nil, err
}
}
spec, err := read(fs, appRoot)
if os.IsNotExist(err) {
......@@ -232,7 +224,7 @@ func cleanEnv(fs afero.Fs, root string) error {
return nil
}
func findRoot(fs afero.Fs, cwd string) (string, error) {
func FindRoot(fs afero.Fs, cwd string) (string, error) {
prev := cwd
for {
......
......@@ -22,7 +22,7 @@ import (
"github.com/stretchr/testify/require"
)
func Test_findRoot(t *testing.T) {
func Test_FindRoot(t *testing.T) {
fs := afero.NewMemMapFs()
stageFile(t, fs, "app010_app.yaml", "/app/app.yaml")
......@@ -61,7 +61,7 @@ func Test_findRoot(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
root, err := findRoot(fs, tc.name)
root, err := FindRoot(fs, tc.name)
if tc.isErr {
require.Error(t, err)
return
......
......@@ -75,7 +75,7 @@ func (i *initApp) Run() error {
}
// 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 {
return err
}
......
......@@ -18,6 +18,7 @@ package clicmd
import (
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/pkg/errors"
"github.com/spf13/viper"
)
type initName int
......@@ -116,3 +117,8 @@ func runAction(name initName, args map[string]interface{}) error {
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
import (
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/ksonnet/ksonnet/pkg/client"
"github.com/pkg/errors"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
......@@ -82,8 +82,8 @@ ks apply dev -c guestbook-ui -c nginx-depl --create false
`
)
func newApplyCmd(a app.App) *cobra.Command {
applyClientConfig := client.NewDefaultClientConfig(a)
func newApplyCmd(fs afero.Fs) *cobra.Command {
applyClientConfig := client.NewDefaultClientConfig()
applyCmd := &cobra.Command{
Use: "apply <env-name> [-c <component-name>] [--dry-run]",
......@@ -95,7 +95,6 @@ func newApplyCmd(a app.App) *cobra.Command {
}
m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionClientConfig: applyClientConfig,
actions.OptionComponentNames: viper.GetStringSlice(vApplyComponent),
actions.OptionCreate: viper.GetBool(vApplyCreate),
......@@ -104,8 +103,9 @@ func newApplyCmd(a app.App) *cobra.Command {
actions.OptionGcTag: viper.GetString(vApplyGcTag),
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")
}
......
......@@ -19,11 +19,10 @@ import (
"fmt"
"strings"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/spf13/cobra"
)
func newComponentCmd(a app.App) *cobra.Command {
func newComponentCmd() *cobra.Command {
componentCmd := &cobra.Command{
Use: "component",
Short: "Manage ksonnet components",
......@@ -35,8 +34,8 @@ func newComponentCmd(a app.App) *cobra.Command {
},
}
componentCmd.AddCommand(newComponentListCmd(a))
componentCmd.AddCommand(newComponentRmCmd(a))
componentCmd.AddCommand(newComponentListCmd())
componentCmd.AddCommand(newComponentRmCmd())
return componentCmd
......
......@@ -19,7 +19,6 @@ import (
"fmt"
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
......@@ -38,7 +37,7 @@ The ` + "`list`" + ` command displays all known components.
ks component list`
)
func newComponentListCmd(a app.App) *cobra.Command {
func newComponentListCmd() *cobra.Command {
componentListCmd := &cobra.Command{
Use: "list",
Short: "List known components",
......@@ -50,10 +49,10 @@ func newComponentListCmd(a app.App) *cobra.Command {
}
m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionModule: viper.GetString(vComponentListNamespace),
actions.OptionOutput: viper.GetString(vComponentListOutput),
}
addGlobalOptions(m)
return runAction(actionComponentList, m)
},
......
......@@ -19,7 +19,6 @@ import (
"fmt"
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/spf13/cobra"
)
......@@ -33,7 +32,7 @@ references throughout the project.`
ks component rm guestbook`
)
func newComponentRmCmd(a app.App) *cobra.Command {
func newComponentRmCmd() *cobra.Command {
componentRmCmd := &cobra.Command{
Use: "rm <component-name>",
Short: "Delete a component from the ksonnet application",
......@@ -45,9 +44,9 @@ func newComponentRmCmd(a app.App) *cobra.Command {
}
m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionComponentName: args[0],
}
addGlobalOptions(m)
return runAction(actionComponentRm, m)
},
......
......@@ -17,9 +17,9 @@ package clicmd
import (
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/ksonnet/ksonnet/pkg/client"
"github.com/pkg/errors"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
......@@ -57,8 +57,8 @@ ks delete dev
ks delete --kubeconfig=./kubeconfig -c nginx`
)
func newDeleteCmd(a app.App) *cobra.Command {
deleteClientConfig := client.NewDefaultClientConfig(a)
func newDeleteCmd(fs afero.Fs) *cobra.Command {
deleteClientConfig := client.NewDefaultClientConfig()
deleteCmd := &cobra.Command{
Use: "delete [env-name] [-c <component-name>]",
......@@ -72,14 +72,14 @@ func newDeleteCmd(a app.App) *cobra.Command {
}
m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionClientConfig: deleteClientConfig,
actions.OptionComponentNames: viper.GetStringSlice(vDeleteComponent),
actions.OptionEnvName: envName,
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")
}
......
......@@ -19,11 +19,11 @@ import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/ksonnet/ksonnet/pkg/client"
)
......@@ -85,8 +85,8 @@ ks diff dev -c redis
`
)
func newDiffCmd(a app.App) *cobra.Command {
diffClientConfig := client.NewDefaultClientConfig(a)
func newDiffCmd(fs afero.Fs) *cobra.Command {
diffClientConfig := client.NewDefaultClientConfig()
diffCmd := &cobra.Command{
Use: "diff <location1:env1> [location2:env2]",
......@@ -102,17 +102,17 @@ func newDiffCmd(a app.App) *cobra.Command {
}
m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionClientConfig: diffClientConfig,
actions.OptionSrc1: args[0],
actions.OptionComponentNames: viper.GetStringSlice(vDiffComponentNames),
}
addGlobalOptions(m)
if len(args) == 2 {
m[actions.OptionSrc2] = args[1]
}
if err := extractJsonnetFlags(a, "diff"); err != nil {
if err := extractJsonnetFlags(fs, "diff"); err != nil {
return errors.Wrap(err, "handle jsonnet flags")
}
......
......@@ -19,7 +19,6 @@ import (
"fmt"
"strings"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/ksonnet/ksonnet/pkg/client"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
......@@ -72,7 +71,7 @@ represented as a hierarchy in the ` + "`environments/`" + ` directory of a ksonn
`
)
func newEnvCmd(a app.App) *cobra.Command {
func newEnvCmd() *cobra.Command {
envCmd := &cobra.Command{
Use: "env",
Short: `Manage ksonnet environments`,
......@@ -85,14 +84,14 @@ func newEnvCmd(a app.App) *cobra.Command {
},
}
envCmd.AddCommand(newEnvAddCmd(a))
envCmd.AddCommand(newEnvCurrentCmd(a))
envCmd.AddCommand(newEnvDescribeCmd(a))
envCmd.AddCommand(newEnvListCmd(a))
envCmd.AddCommand(newEnvRmCmd(a))
envCmd.AddCommand(newEnvSetCmd(a))
envCmd.AddCommand(newEnvTargetsCmd(a))
envCmd.AddCommand(newEnvUpdateCmd(a))
envCmd.AddCommand(newEnvAddCmd())
envCmd.AddCommand(newEnvCurrentCmd())
envCmd.AddCommand(newEnvDescribeCmd())
envCmd.AddCommand(newEnvListCmd())
envCmd.AddCommand(newEnvRmCmd())
envCmd.AddCommand(newEnvSetCmd())
envCmd.AddCommand(newEnvTargetsCmd())
envCmd.AddCommand(newEnvUpdateCmd())
return envCmd
......
......@@ -21,7 +21,6 @@ import (
"github.com/spf13/viper"
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/ksonnet/ksonnet/pkg/client"
"github.com/spf13/cobra"
)
......@@ -81,8 +80,8 @@ ks env add my-env --context=dev
ks env add prod --server=https://ksonnet-1.us-west.elb.amazonaws.com`
)
func newEnvAddCmd(a app.App) *cobra.Command {
envClientConfig := client.NewDefaultClientConfig(a)
func newEnvAddCmd() *cobra.Command {
envClientConfig := client.NewDefaultClientConfig()
envAddCmd := &cobra.Command{
Use: "add <env-name>",
......@@ -115,13 +114,13 @@ func newEnvAddCmd(a app.App) *cobra.Command {
isOverride := viper.GetBool(vEnvAddOverride)
m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionEnvName: name,
actions.OptionServer: server,
actions.OptionModule: namespace,
actions.OptionSpecFlag: specFlag,
actions.OptionOverride: isOverride,
}
addGlobalOptions(m)
return runAction(actionEnvAdd, m)
},
......
......@@ -19,7 +19,6 @@ import (
"fmt"
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
......@@ -49,7 +48,7 @@ ks env current
ks env current --unset`
)
func newEnvCurrentCmd(a app.App) *cobra.Command {
func newEnvCurrentCmd() *cobra.Command {
envCurrentCmd := &cobra.Command{
Use: "current [--set <name> | --unset]",
Short: envShortDesc["current"],
......@@ -61,10 +60,10 @@ func newEnvCurrentCmd(a app.App) *cobra.Command {
}
m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionEnvName: viper.GetString(vEnvCurrentSet),
actions.OptionUnset: viper.GetBool(vEnvCurrentUnset),
}
addGlobalOptions(m)
return runAction(actionEnvCurrent, m)
},
......
......@@ -17,12 +17,11 @@ package clicmd
import (
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
func newEnvDescribeCmd(a app.App) *cobra.Command {
func newEnvDescribeCmd() *cobra.Command {
envDescribeCmd := &cobra.Command{
Use: "describe <env>",
Short: "Describe an environment",
......@@ -33,9 +32,9 @@ func newEnvDescribeCmd(a app.App) *cobra.Command {
}
m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionEnvName: args[0],
}
addGlobalOptions(m)
return runAction(actionEnvDescribe, m)
},
......
......@@ -19,7 +19,6 @@ import (
"fmt"
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
......@@ -44,7 +43,7 @@ current ksonnet app. Specifically, this will display the (1) *name*,
`
)
func newEnvListCmd(a app.App) *cobra.Command {
func newEnvListCmd() *cobra.Command {
envListCmd := &cobra.Command{
Use: "list",
Short: envShortDesc["list"],
......@@ -55,9 +54,9 @@ func newEnvListCmd(a app.App) *cobra.Command {
}
m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionOutput: viper.GetString(vEnvListOutput),
}
addGlobalOptions(m)
return runAction(actionEnvList, m)
},
......
......@@ -19,7 +19,6 @@ import (
"fmt"
"github.com/ksonnet/ksonnet/pkg/actions"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/spf13/cobra"