From bee7a16cb075cfc245cefc95109602869efb77ed Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 27 Jul 2023 12:03:08 +0200 Subject: [PATCH] Remove dependency on global state for remaining commands (#613) ## Changes This removes the remaining dependency on global state and unblocks work to parallelize integration tests. As is, we can already uncomment an integration test that had to be skipped because of other tests tainting global state. This is no longer an issue. Also see #595 and #606. ## Tests * Unit and integration tests pass. * Manually confirmed the help output is the same. --- cmd/api/api.go | 32 +++++++-------- cmd/auth/auth.go | 27 +++++++------ cmd/auth/env.go | 27 ++++++------- cmd/auth/login.go | 36 ++++++++--------- cmd/auth/profiles.go | 39 ++++++++++--------- cmd/auth/token.go | 25 ++++++------ cmd/bundle/bundle.go | 23 +++++++++++ cmd/bundle/debug/debug.go | 19 --------- cmd/bundle/debug/whoami.go | 30 -------------- cmd/bundle/deploy.go | 28 ++++++------- cmd/bundle/destroy.go | 29 +++++++------- cmd/bundle/launch.go | 27 ++++++------- cmd/bundle/root.go | 23 ----------- cmd/bundle/run.go | 34 ++++++++-------- cmd/bundle/schema.go | 26 ++++++------- cmd/bundle/sync.go | 49 ++++++++++++----------- cmd/bundle/test.go | 25 ++++++------ cmd/bundle/validate.go | 19 ++++----- cmd/bundle/variables.go | 9 ++++- cmd/cmd.go | 52 +++++++++++++------------ cmd/configure/configure.go | 49 ++++++++++++----------- cmd/configure/configure_test.go | 19 +++++---- cmd/fs/cat.go | 22 +++++------ cmd/fs/cp.go | 69 ++++++++++++++++----------------- cmd/fs/fs.go | 24 +++++++----- cmd/fs/ls.go | 37 +++++++++--------- cmd/fs/mkdir.go | 28 ++++++------- cmd/fs/rm.go | 28 ++++++------- cmd/root/root.go | 3 -- cmd/sync/sync.go | 67 +++++++++++++++++--------------- cmd/sync/sync_test.go | 13 ++++--- cmd/version/version.go | 23 ++++++----- internal/secrets_test.go | 7 ---- main.go | 10 ----- 34 files changed, 476 insertions(+), 502 deletions(-) create mode 100644 cmd/bundle/bundle.go delete mode 100644 cmd/bundle/debug/debug.go delete mode 100644 cmd/bundle/debug/whoami.go delete mode 100644 cmd/bundle/root.go diff --git a/cmd/api/api.go b/cmd/api/api.go index 563efa73..698781e6 100644 --- a/cmd/api/api.go +++ b/cmd/api/api.go @@ -5,7 +5,6 @@ import ( "net/http" "strings" - "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/flags" "github.com/databricks/databricks-sdk-go/client" @@ -13,9 +12,22 @@ import ( "github.com/spf13/cobra" ) -var apiCmd = &cobra.Command{ - Use: "api", - Short: "Perform Databricks API call", +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "api", + Short: "Perform Databricks API call", + } + + cmd.AddCommand( + makeCommand(http.MethodGet), + makeCommand(http.MethodHead), + makeCommand(http.MethodPost), + makeCommand(http.MethodPut), + makeCommand(http.MethodPatch), + makeCommand(http.MethodDelete), + ) + + return cmd } func makeCommand(method string) *cobra.Command { @@ -59,15 +71,3 @@ func makeCommand(method string) *cobra.Command { command.Flags().Var(&payload, "json", `either inline JSON string or @path/to/file.json with request body`) return command } - -func init() { - apiCmd.AddCommand( - makeCommand(http.MethodGet), - makeCommand(http.MethodHead), - makeCommand(http.MethodPost), - makeCommand(http.MethodPut), - makeCommand(http.MethodPatch), - makeCommand(http.MethodDelete), - ) - root.RootCmd.AddCommand(apiCmd) -} diff --git a/cmd/auth/auth.go b/cmd/auth/auth.go index b7e8d2d7..e0c7c7c5 100644 --- a/cmd/auth/auth.go +++ b/cmd/auth/auth.go @@ -3,18 +3,27 @@ package auth import ( "context" - "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/cmdio" "github.com/spf13/cobra" ) -var authCmd = &cobra.Command{ - Use: "auth", - Short: "Authentication related commands", -} +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "auth", + Short: "Authentication related commands", + } -var persistentAuth auth.PersistentAuth + var perisistentAuth auth.PersistentAuth + cmd.PersistentFlags().StringVar(&perisistentAuth.Host, "host", perisistentAuth.Host, "Databricks Host") + cmd.PersistentFlags().StringVar(&perisistentAuth.AccountID, "account-id", perisistentAuth.AccountID, "Databricks Account ID") + + cmd.AddCommand(newEnvCommand()) + cmd.AddCommand(newLoginCommand(&perisistentAuth)) + cmd.AddCommand(newProfilesCommand()) + cmd.AddCommand(newTokenCommand(&perisistentAuth)) + return cmd +} func promptForHost(ctx context.Context) (string, error) { prompt := cmdio.Prompt(ctx) @@ -41,9 +50,3 @@ func promptForAccountID(ctx context.Context) (string, error) { } return accountId, nil } - -func init() { - root.RootCmd.AddCommand(authCmd) - authCmd.PersistentFlags().StringVar(&persistentAuth.Host, "host", persistentAuth.Host, "Databricks Host") - authCmd.PersistentFlags().StringVar(&persistentAuth.AccountID, "account-id", persistentAuth.AccountID, "Databricks Account ID") -} diff --git a/cmd/auth/env.go b/cmd/auth/env.go index e288c576..7bf3fd91 100644 --- a/cmd/auth/env.go +++ b/cmd/auth/env.go @@ -89,10 +89,18 @@ func loadFromDatabricksCfg(cfg *config.Config) error { return nil } -var envCmd = &cobra.Command{ - Use: "env", - Short: "Get env", - RunE: func(cmd *cobra.Command, args []string) error { +func newEnvCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "env", + Short: "Get env", + } + + var host string + var profile string + cmd.Flags().StringVar(&host, "host", host, "Hostname to get auth env for") + cmd.Flags().StringVar(&profile, "profile", profile, "Profile to get auth env for") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { cfg := &config.Config{ Host: host, Profile: profile, @@ -130,14 +138,7 @@ var envCmd = &cobra.Command{ } cmd.OutOrStdout().Write(raw) return nil - }, -} + } -var host string -var profile string - -func init() { - authCmd.AddCommand(envCmd) - envCmd.Flags().StringVar(&host, "host", host, "Hostname to get auth env for") - envCmd.Flags().StringVar(&profile, "profile", profile, "Profile to get auth env for") + return cmd } diff --git a/cmd/auth/login.go b/cmd/auth/login.go index 37d44c08..fcb0e0dd 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -14,10 +14,7 @@ import ( "github.com/spf13/cobra" ) -var loginTimeout time.Duration -var configureCluster bool - -func configureHost(ctx context.Context, args []string, argIndex int) error { +func configureHost(ctx context.Context, persistentAuth *auth.PersistentAuth, args []string, argIndex int) error { if len(args) > argIndex { persistentAuth.Host = args[argIndex] return nil @@ -31,13 +28,23 @@ func configureHost(ctx context.Context, args []string, argIndex int) error { return nil } -var loginCmd = &cobra.Command{ - Use: "login [HOST]", - Short: "Authenticate this machine", - RunE: func(cmd *cobra.Command, args []string) error { +func newLoginCommand(persistentAuth *auth.PersistentAuth) *cobra.Command { + cmd := &cobra.Command{ + Use: "login [HOST]", + Short: "Authenticate this machine", + } + + var loginTimeout time.Duration + var configureCluster bool + cmd.Flags().DurationVar(&loginTimeout, "timeout", auth.DefaultTimeout, + "Timeout for completing login challenge in the browser") + cmd.Flags().BoolVar(&configureCluster, "configure-cluster", false, + "Prompts to configure cluster") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() if persistentAuth.Host == "" { - configureHost(ctx, args, 0) + configureHost(ctx, persistentAuth, args, 0) } defer persistentAuth.Close() @@ -108,14 +115,7 @@ var loginCmd = &cobra.Command{ cmdio.LogString(ctx, fmt.Sprintf("Profile %s was successfully saved", profileName)) return nil - }, -} + } -func init() { - authCmd.AddCommand(loginCmd) - loginCmd.Flags().DurationVar(&loginTimeout, "timeout", auth.DefaultTimeout, - "Timeout for completing login challenge in the browser") - - loginCmd.Flags().BoolVar(&configureCluster, "configure-cluster", false, - "Prompts to configure cluster") + return cmd } diff --git a/cmd/auth/profiles.go b/cmd/auth/profiles.go index d3b167b7..2b08164f 100644 --- a/cmd/auth/profiles.go +++ b/cmd/auth/profiles.go @@ -44,7 +44,7 @@ func (c *profileMetadata) IsEmpty() bool { return c.Host == "" && c.AccountID == "" } -func (c *profileMetadata) Load(ctx context.Context) { +func (c *profileMetadata) Load(ctx context.Context, skipValidate bool) { // TODO: disable config loaders other than configfile cfg := &config.Config{Profile: c.Name} _ = cfg.EnsureResolved() @@ -94,16 +94,22 @@ func (c *profileMetadata) Load(ctx context.Context) { c.Host = cfg.Host } -var profilesCmd = &cobra.Command{ - Use: "profiles", - Short: "Lists profiles from ~/.databrickscfg", - Annotations: map[string]string{ - "template": cmdio.Heredoc(` - {{header "Name"}} {{header "Host"}} {{header "Valid"}} - {{range .Profiles}}{{.Name | green}} {{.Host|cyan}} {{bool .Valid}} - {{end}}`), - }, - RunE: func(cmd *cobra.Command, args []string) error { +func newProfilesCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "profiles", + Short: "Lists profiles from ~/.databrickscfg", + Annotations: map[string]string{ + "template": cmdio.Heredoc(` + {{header "Name"}} {{header "Host"}} {{header "Valid"}} + {{range .Profiles}}{{.Name | green}} {{.Host|cyan}} {{bool .Valid}} + {{end}}`), + }, + } + + var skipValidate bool + cmd.Flags().BoolVar(&skipValidate, "skip-validate", false, "Whether to skip validating the profiles") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { var profiles []*profileMetadata iniFile, err := getDatabricksCfg() if os.IsNotExist(err) { @@ -126,7 +132,7 @@ var profilesCmd = &cobra.Command{ wg.Add(1) go func() { // load more information about profile - profile.Load(cmd.Context()) + profile.Load(cmd.Context(), skipValidate) wg.Done() }() profiles = append(profiles, profile) @@ -135,12 +141,7 @@ var profilesCmd = &cobra.Command{ return cmdio.Render(cmd.Context(), struct { Profiles []*profileMetadata `json:"profiles"` }{profiles}) - }, -} + } -var skipValidate bool - -func init() { - authCmd.AddCommand(profilesCmd) - profilesCmd.Flags().BoolVar(&skipValidate, "skip-validate", false, "Whether to skip validating the profiles") + return cmd } diff --git a/cmd/auth/token.go b/cmd/auth/token.go index 1b8d8b13..242a3dab 100644 --- a/cmd/auth/token.go +++ b/cmd/auth/token.go @@ -9,15 +9,20 @@ import ( "github.com/spf13/cobra" ) -var tokenTimeout time.Duration +func newTokenCommand(persistentAuth *auth.PersistentAuth) *cobra.Command { + cmd := &cobra.Command{ + Use: "token [HOST]", + Short: "Get authentication token", + } -var tokenCmd = &cobra.Command{ - Use: "token [HOST]", - Short: "Get authentication token", - RunE: func(cmd *cobra.Command, args []string) error { + var tokenTimeout time.Duration + cmd.Flags().DurationVar(&tokenTimeout, "timeout", auth.DefaultTimeout, + "Timeout for acquiring a token.") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() if persistentAuth.Host == "" { - configureHost(ctx, args, 0) + configureHost(ctx, persistentAuth, args, 0) } defer persistentAuth.Close() @@ -33,11 +38,7 @@ var tokenCmd = &cobra.Command{ } cmd.OutOrStdout().Write(raw) return nil - }, -} + } -func init() { - authCmd.AddCommand(tokenCmd) - tokenCmd.Flags().DurationVar(&tokenTimeout, "timeout", auth.DefaultTimeout, - "Timeout for acquiring a token.") + return cmd } diff --git a/cmd/bundle/bundle.go b/cmd/bundle/bundle.go new file mode 100644 index 00000000..8d1216f8 --- /dev/null +++ b/cmd/bundle/bundle.go @@ -0,0 +1,23 @@ +package bundle + +import ( + "github.com/spf13/cobra" +) + +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "bundle", + Short: "Databricks Asset Bundles", + } + + initVariableFlag(cmd) + cmd.AddCommand(newDeployCommand()) + cmd.AddCommand(newDestroyCommand()) + cmd.AddCommand(newLaunchCommand()) + cmd.AddCommand(newRunCommand()) + cmd.AddCommand(newSchemaCommand()) + cmd.AddCommand(newSyncCommand()) + cmd.AddCommand(newTestCommand()) + cmd.AddCommand(newValidateCommand()) + return cmd +} diff --git a/cmd/bundle/debug/debug.go b/cmd/bundle/debug/debug.go deleted file mode 100644 index fdc894ef..00000000 --- a/cmd/bundle/debug/debug.go +++ /dev/null @@ -1,19 +0,0 @@ -package debug - -import ( - "github.com/spf13/cobra" - - parent "github.com/databricks/cli/cmd/bundle" -) - -var debugCmd = &cobra.Command{ - Use: "debug", -} - -func AddCommand(cmd *cobra.Command) { - debugCmd.AddCommand(cmd) -} - -func init() { - parent.AddCommand(debugCmd) -} diff --git a/cmd/bundle/debug/whoami.go b/cmd/bundle/debug/whoami.go deleted file mode 100644 index 95d97eeb..00000000 --- a/cmd/bundle/debug/whoami.go +++ /dev/null @@ -1,30 +0,0 @@ -package debug - -import ( - "fmt" - - "github.com/databricks/cli/bundle" - bundleCmd "github.com/databricks/cli/cmd/bundle" - "github.com/spf13/cobra" -) - -var whoamiCmd = &cobra.Command{ - Use: "whoami", - - PreRunE: bundleCmd.ConfigureBundleWithVariables, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - w := bundle.Get(ctx).WorkspaceClient() - user, err := w.CurrentUser.Me(ctx) - if err != nil { - return err - } - - fmt.Fprintln(cmd.OutOrStdout(), user.UserName) - return nil - }, -} - -func init() { - debugCmd.AddCommand(whoamiCmd) -} diff --git a/cmd/bundle/deploy.go b/cmd/bundle/deploy.go index e8c0d395..a39f1996 100644 --- a/cmd/bundle/deploy.go +++ b/cmd/bundle/deploy.go @@ -6,12 +6,19 @@ import ( "github.com/spf13/cobra" ) -var deployCmd = &cobra.Command{ - Use: "deploy", - Short: "Deploy bundle", +func newDeployCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "deploy", + Short: "Deploy bundle", + PreRunE: ConfigureBundleWithVariables, + } - PreRunE: ConfigureBundleWithVariables, - RunE: func(cmd *cobra.Command, args []string) error { + var forceDeploy bool + var computeID string + cmd.Flags().BoolVar(&forceDeploy, "force", false, "Force acquisition of deployment lock.") + cmd.Flags().StringVarP(&computeID, "compute-id", "c", "", "Override compute in the deployment with the given compute ID.") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { b := bundle.Get(cmd.Context()) // If `--force` is specified, force acquisition of the deployment lock. @@ -23,14 +30,7 @@ var deployCmd = &cobra.Command{ phases.Build(), phases.Deploy(), )) - }, -} + } -var forceDeploy bool -var computeID string - -func init() { - AddCommand(deployCmd) - deployCmd.Flags().BoolVar(&forceDeploy, "force", false, "Force acquisition of deployment lock.") - deployCmd.Flags().StringVarP(&computeID, "compute-id", "c", "", "Override compute in the deployment with the given compute ID.") + return cmd } diff --git a/cmd/bundle/destroy.go b/cmd/bundle/destroy.go index d0fe699a..82d82144 100644 --- a/cmd/bundle/destroy.go +++ b/cmd/bundle/destroy.go @@ -12,12 +12,20 @@ import ( "golang.org/x/term" ) -var destroyCmd = &cobra.Command{ - Use: "destroy", - Short: "Destroy deployed bundle resources", +func newDestroyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "destroy", + Short: "Destroy deployed bundle resources", - PreRunE: ConfigureBundleWithVariables, - RunE: func(cmd *cobra.Command, args []string) error { + PreRunE: ConfigureBundleWithVariables, + } + + var autoApprove bool + var forceDestroy bool + cmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Skip interactive approvals for deleting resources and files") + cmd.Flags().BoolVar(&forceDestroy, "force", false, "Force acquisition of deployment lock.") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() b := bundle.Get(ctx) @@ -47,14 +55,7 @@ var destroyCmd = &cobra.Command{ phases.Build(), phases.Destroy(), )) - }, -} + } -var autoApprove bool -var forceDestroy bool - -func init() { - AddCommand(destroyCmd) - destroyCmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Skip interactive approvals for deleting resources and files") - destroyCmd.Flags().BoolVar(&forceDestroy, "force", false, "Force acquisition of deployment lock.") + return cmd } diff --git a/cmd/bundle/launch.go b/cmd/bundle/launch.go index ae44352e..bbb43600 100644 --- a/cmd/bundle/launch.go +++ b/cmd/bundle/launch.go @@ -7,17 +7,20 @@ import ( "github.com/spf13/cobra" ) -var launchCmd = &cobra.Command{ - Use: "launch", - Short: "Launches a notebook on development cluster", - Long: `Reads a file and executes it on dev cluster`, - Args: cobra.ExactArgs(1), +func newLaunchCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "launch", + Short: "Launches a notebook on development cluster", + Long: `Reads a file and executes it on dev cluster`, + Args: cobra.ExactArgs(1), - // We're not ready to expose this command until we specify its semantics. - Hidden: true, + // We're not ready to expose this command until we specify its semantics. + Hidden: true, - PreRunE: root.MustConfigureBundle, - RunE: func(cmd *cobra.Command, args []string) error { + PreRunE: root.MustConfigureBundle, + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { return fmt.Errorf("TODO") // contents, err := os.ReadFile(args[0]) // if err != nil { @@ -29,9 +32,7 @@ var launchCmd = &cobra.Command{ // } // fmt.Fprintf(cmd.OutOrStdout(), "Success: %s", results.Text()) // return nil - }, -} + } -func init() { - AddCommand(launchCmd) + return cmd } diff --git a/cmd/bundle/root.go b/cmd/bundle/root.go deleted file mode 100644 index 395ed383..00000000 --- a/cmd/bundle/root.go +++ /dev/null @@ -1,23 +0,0 @@ -package bundle - -import ( - "github.com/databricks/cli/cmd/root" - "github.com/spf13/cobra" -) - -// rootCmd represents the root command for the bundle subcommand. -var rootCmd = &cobra.Command{ - Use: "bundle", - Short: "Databricks Asset Bundles", -} - -func AddCommand(cmd *cobra.Command) { - rootCmd.AddCommand(cmd) -} - -var variables []string - -func init() { - root.RootCmd.AddCommand(rootCmd) - AddVariableFlag(rootCmd) -} diff --git a/cmd/bundle/run.go b/cmd/bundle/run.go index 439e3522..28b9ae7c 100644 --- a/cmd/bundle/run.go +++ b/cmd/bundle/run.go @@ -13,16 +13,22 @@ import ( "github.com/spf13/cobra" ) -var runOptions run.Options -var noWait bool +func newRunCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "run [flags] KEY", + Short: "Run a workload (e.g. a job or a pipeline)", -var runCmd = &cobra.Command{ - Use: "run [flags] KEY", - Short: "Run a workload (e.g. a job or a pipeline)", + Args: cobra.ExactArgs(1), + PreRunE: ConfigureBundleWithVariables, + } - Args: cobra.ExactArgs(1), - PreRunE: ConfigureBundleWithVariables, - RunE: func(cmd *cobra.Command, args []string) error { + var runOptions run.Options + runOptions.Define(cmd.Flags()) + + var noWait bool + cmd.Flags().BoolVar(&noWait, "no-wait", false, "Don't wait for the run to complete.") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { b := bundle.Get(cmd.Context()) err := bundle.Apply(cmd.Context(), b, bundle.Seq( @@ -65,9 +71,9 @@ var runCmd = &cobra.Command{ } } return nil - }, + } - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) > 0 { return nil, cobra.ShellCompDirectiveNoFileComp } @@ -86,11 +92,7 @@ var runCmd = &cobra.Command{ } return run.ResourceCompletions(b), cobra.ShellCompDirectiveNoFileComp - }, -} + } -func init() { - runOptions.Define(runCmd.Flags()) - rootCmd.AddCommand(runCmd) - runCmd.Flags().BoolVar(&noWait, "no-wait", false, "Don't wait for the run to complete.") + return cmd } diff --git a/cmd/bundle/schema.go b/cmd/bundle/schema.go index b288d78e..8b2c0177 100644 --- a/cmd/bundle/schema.go +++ b/cmd/bundle/schema.go @@ -9,11 +9,18 @@ import ( "github.com/spf13/cobra" ) -var schemaCmd = &cobra.Command{ - Use: "schema", - Short: "Generate JSON Schema for bundle configuration", +func newSchemaCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "schema", + Short: "Generate JSON Schema for bundle configuration", + } - RunE: func(cmd *cobra.Command, args []string) error { + var openapi string + var onlyDocs bool + cmd.Flags().StringVar(&openapi, "openapi", "", "path to a databricks openapi spec") + cmd.Flags().BoolVar(&onlyDocs, "only-docs", false, "only generate descriptions for the schema") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { docs, err := schema.BundleDocs(openapi) if err != nil { return err @@ -34,14 +41,7 @@ var schemaCmd = &cobra.Command{ } cmd.OutOrStdout().Write(result) return nil - }, -} + } -var openapi string -var onlyDocs bool - -func init() { - AddCommand(schemaCmd) - schemaCmd.Flags().StringVar(&openapi, "openapi", "", "path to a databricks openapi spec") - schemaCmd.Flags().BoolVar(&onlyDocs, "only-docs", false, "only generate descriptions for the schema") + return cmd } diff --git a/cmd/bundle/sync.go b/cmd/bundle/sync.go index 19adc2dd..2fff7baf 100644 --- a/cmd/bundle/sync.go +++ b/cmd/bundle/sync.go @@ -11,7 +11,13 @@ import ( "github.com/spf13/cobra" ) -func syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) (*sync.SyncOptions, error) { +type syncFlags struct { + interval time.Duration + full bool + watch bool +} + +func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) (*sync.SyncOptions, error) { cacheDir, err := b.CacheDir() if err != nil { return nil, fmt.Errorf("cannot get bundle cache directory: %w", err) @@ -20,8 +26,8 @@ func syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) (*sync.SyncOpti opts := sync.SyncOptions{ LocalPath: b.Config.Path, RemotePath: b.Config.Workspace.FilesPath, - Full: full, - PollInterval: interval, + Full: f.full, + PollInterval: f.interval, SnapshotBasePath: cacheDir, WorkspaceClient: b.WorkspaceClient(), @@ -29,13 +35,21 @@ func syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) (*sync.SyncOpti return &opts, nil } -var syncCmd = &cobra.Command{ - Use: "sync [flags]", - Short: "Synchronize bundle tree to the workspace", - Args: cobra.NoArgs, +func newSyncCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "sync [flags]", + Short: "Synchronize bundle tree to the workspace", + Args: cobra.NoArgs, - PreRunE: ConfigureBundleWithVariables, - RunE: func(cmd *cobra.Command, args []string) error { + PreRunE: ConfigureBundleWithVariables, + } + + var f syncFlags + cmd.Flags().DurationVar(&f.interval, "interval", 1*time.Second, "file system polling interval (for --watch)") + cmd.Flags().BoolVar(&f.full, "full", false, "perform full synchronization (default is incremental)") + cmd.Flags().BoolVar(&f.watch, "watch", false, "watch local file system for changes") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { b := bundle.Get(cmd.Context()) // Run initialize phase to make sure paths are set. @@ -44,7 +58,7 @@ var syncCmd = &cobra.Command{ return err } - opts, err := syncOptionsFromBundle(cmd, b) + opts, err := f.syncOptionsFromBundle(cmd, b) if err != nil { return err } @@ -57,21 +71,12 @@ var syncCmd = &cobra.Command{ log.Infof(ctx, "Remote file sync location: %v", opts.RemotePath) - if watch { + if f.watch { return s.RunContinuous(ctx) } return s.RunOnce(ctx) - }, -} + } -var interval time.Duration -var full bool -var watch bool - -func init() { - AddCommand(syncCmd) - syncCmd.Flags().DurationVar(&interval, "interval", 1*time.Second, "file system polling interval (for --watch)") - syncCmd.Flags().BoolVar(&full, "full", false, "perform full synchronization (default is incremental)") - syncCmd.Flags().BoolVar(&watch, "watch", false, "watch local file system for changes") + return cmd } diff --git a/cmd/bundle/test.go b/cmd/bundle/test.go index ec36f18a..ea1a4b71 100644 --- a/cmd/bundle/test.go +++ b/cmd/bundle/test.go @@ -7,16 +7,19 @@ import ( "github.com/spf13/cobra" ) -var testCmd = &cobra.Command{ - Use: "test", - Short: "run tests for the project", - Long: `This is longer description of the command`, +func newTestCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "test", + Short: "run tests for the project", + Long: `This is longer description of the command`, - // We're not ready to expose this command until we specify its semantics. - Hidden: true, + // We're not ready to expose this command until we specify its semantics. + Hidden: true, - PreRunE: root.MustConfigureBundle, - RunE: func(cmd *cobra.Command, args []string) error { + PreRunE: root.MustConfigureBundle, + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { return fmt.Errorf("TODO") // results := project.RunPythonOnDev(cmd.Context(), `return 1`) // if results.Failed() { @@ -24,9 +27,7 @@ var testCmd = &cobra.Command{ // } // fmt.Fprintf(cmd.OutOrStdout(), "Success: %s", results.Text()) // return nil - }, -} + } -func init() { - AddCommand(testCmd) + return cmd } diff --git a/cmd/bundle/validate.go b/cmd/bundle/validate.go index 65ab3890..b98cbd52 100644 --- a/cmd/bundle/validate.go +++ b/cmd/bundle/validate.go @@ -8,12 +8,15 @@ import ( "github.com/spf13/cobra" ) -var validateCmd = &cobra.Command{ - Use: "validate", - Short: "Validate configuration", +func newValidateCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "validate", + Short: "Validate configuration", - PreRunE: ConfigureBundleWithVariables, - RunE: func(cmd *cobra.Command, args []string) error { + PreRunE: ConfigureBundleWithVariables, + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { b := bundle.Get(cmd.Context()) err := bundle.Apply(cmd.Context(), b, phases.Initialize()) @@ -27,9 +30,7 @@ var validateCmd = &cobra.Command{ } cmd.OutOrStdout().Write(buf) return nil - }, -} + } -func init() { - AddCommand(validateCmd) + return cmd } diff --git a/cmd/bundle/variables.go b/cmd/bundle/variables.go index b1ab74fe..33f557cc 100644 --- a/cmd/bundle/variables.go +++ b/cmd/bundle/variables.go @@ -13,11 +13,16 @@ func ConfigureBundleWithVariables(cmd *cobra.Command, args []string) error { return err } + variables, err := cmd.Flags().GetStringSlice("var") + if err != nil { + return err + } + // Initialize variables by assigning them values passed as command line flags b := bundle.Get(cmd.Context()) return b.Config.InitializeVariables(variables) } -func AddVariableFlag(cmd *cobra.Command) { - cmd.PersistentFlags().StringSliceVar(&variables, "var", []string{}, `set values for variables defined in bundle config. Example: --var="foo=bar"`) +func initVariableFlag(cmd *cobra.Command) { + cmd.PersistentFlags().StringSlice("var", []string{}, `set values for variables defined in bundle config. Example: --var="foo=bar"`) } diff --git a/cmd/cmd.go b/cmd/cmd.go index 69502d50..04d7cc80 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,40 +1,44 @@ package cmd import ( - "sync" - "github.com/databricks/cli/cmd/account" + "github.com/databricks/cli/cmd/api" + "github.com/databricks/cli/cmd/auth" + "github.com/databricks/cli/cmd/bundle" + "github.com/databricks/cli/cmd/configure" + "github.com/databricks/cli/cmd/fs" "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/cmd/sync" + "github.com/databricks/cli/cmd/version" "github.com/databricks/cli/cmd/workspace" "github.com/spf13/cobra" ) -var once sync.Once -var cmd *cobra.Command - func New() *cobra.Command { - // TODO: this command is still a global. - // Once the non-generated commands are all instantiatable, - // we can remove the global and instantiate this as well. - once.Do(func() { - cli := root.RootCmd + cli := root.New() - // Add account subcommand. - cli.AddCommand(account.New()) + // Add account subcommand. + cli.AddCommand(account.New()) - // Add workspace subcommands. - for _, cmd := range workspace.All() { - cli.AddCommand(cmd) - } + // Add workspace subcommands. + for _, cmd := range workspace.All() { + cli.AddCommand(cmd) + } - // Add workspace command groups. - groups := workspace.Groups() - for i := range groups { - cli.AddGroup(&groups[i]) - } + // Add workspace command groups. + groups := workspace.Groups() + for i := range groups { + cli.AddGroup(&groups[i]) + } - cmd = cli - }) + // Add other subcommands. + cli.AddCommand(api.New()) + cli.AddCommand(auth.New()) + cli.AddCommand(bundle.New()) + cli.AddCommand(configure.New()) + cli.AddCommand(fs.New()) + cli.AddCommand(sync.New()) + cli.AddCommand(version.New()) - return cmd + return cli } diff --git a/cmd/configure/configure.go b/cmd/configure/configure.go index 14101d59..c51fd830 100644 --- a/cmd/configure/configure.go +++ b/cmd/configure/configure.go @@ -5,7 +5,6 @@ import ( "fmt" "net/url" - "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/databricks-sdk-go/config" @@ -112,19 +111,30 @@ func configureNonInteractive(cmd *cobra.Command, ctx context.Context, cfg *confi return nil } -var configureCmd = &cobra.Command{ - Use: "configure", - Short: "Configure authentication", - Long: `Configure authentication. +func newConfigureCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "configure", + Short: "Configure authentication", + Long: `Configure authentication. -This command adds a profile to your ~/.databrickscfg file. -You can write to a different file by setting the DATABRICKS_CONFIG_FILE environment variable. + This command adds a profile to your ~/.databrickscfg file. + You can write to a different file by setting the DATABRICKS_CONFIG_FILE environment variable. -If this command is invoked in non-interactive mode, it will read the token from stdin. -The host must be specified with the --host flag. - `, - Hidden: true, - RunE: func(cmd *cobra.Command, args []string) error { + If this command is invoked in non-interactive mode, it will read the token from stdin. + The host must be specified with the --host flag. + `, + Hidden: true, + } + + cmd.Flags().String("host", "", "Databricks workspace host.") + cmd.Flags().String("profile", "DEFAULT", "Name for the connection profile to configure.") + + // Include token flag for compatibility with the legacy CLI. + // It doesn't actually do anything because we always use PATs. + cmd.Flags().BoolP("token", "t", true, "Configure using Databricks Personal Access Token") + cmd.Flags().MarkHidden("token") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { var cfg config.Config // Load environment variables, possibly the DEFAULT profile. @@ -152,16 +162,11 @@ The host must be specified with the --host flag. // Save profile to config file. return databrickscfg.SaveToProfile(ctx, &cfg) - }, + } + + return cmd } -func init() { - root.RootCmd.AddCommand(configureCmd) - configureCmd.Flags().String("host", "", "Databricks workspace host.") - configureCmd.Flags().String("profile", "DEFAULT", "Name for the connection profile to configure.") - - // Include token flag for compatibility with the legacy CLI. - // It doesn't actually do anything because we always use PATs. - configureCmd.Flags().BoolP("token", "t", true, "Configure using Databricks Personal Access Token") - configureCmd.Flags().MarkHidden("token") +func New() *cobra.Command { + return newConfigureCommand() } diff --git a/cmd/configure/configure_test.go b/cmd/configure/configure_test.go index 7b627ba9..e1ebe916 100644 --- a/cmd/configure/configure_test.go +++ b/cmd/configure/configure_test.go @@ -1,4 +1,4 @@ -package configure +package configure_test import ( "context" @@ -7,7 +7,7 @@ import ( "runtime" "testing" - "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/cmd" "github.com/stretchr/testify/assert" "gopkg.in/ini.v1" ) @@ -54,9 +54,10 @@ func TestDefaultConfigureNoInteractive(t *testing.T) { }) os.Stdin = inp - root.RootCmd.SetArgs([]string{"configure", "--token", "--host", "https://host"}) + cmd := cmd.New() + cmd.SetArgs([]string{"configure", "--token", "--host", "https://host"}) - err := root.RootCmd.ExecuteContext(ctx) + err := cmd.ExecuteContext(ctx) assert.NoError(t, err) cfgPath := filepath.Join(tempHomeDir, ".databrickscfg") @@ -86,9 +87,10 @@ func TestConfigFileFromEnvNoInteractive(t *testing.T) { t.Cleanup(func() { os.Stdin = oldStdin }) os.Stdin = inp - root.RootCmd.SetArgs([]string{"configure", "--token", "--host", "https://host"}) + cmd := cmd.New() + cmd.SetArgs([]string{"configure", "--token", "--host", "https://host"}) - err := root.RootCmd.ExecuteContext(ctx) + err := cmd.ExecuteContext(ctx) assert.NoError(t, err) _, err = os.Stat(cfgPath) @@ -114,9 +116,10 @@ func TestCustomProfileConfigureNoInteractive(t *testing.T) { t.Cleanup(func() { os.Stdin = oldStdin }) os.Stdin = inp - root.RootCmd.SetArgs([]string{"configure", "--token", "--host", "https://host", "--profile", "CUSTOM"}) + cmd := cmd.New() + cmd.SetArgs([]string{"configure", "--token", "--host", "https://host", "--profile", "CUSTOM"}) - err := root.RootCmd.ExecuteContext(ctx) + err := cmd.ExecuteContext(ctx) assert.NoError(t, err) _, err = os.Stat(cfgPath) diff --git a/cmd/fs/cat.go b/cmd/fs/cat.go index 2cdc4075..8227cd78 100644 --- a/cmd/fs/cat.go +++ b/cmd/fs/cat.go @@ -6,14 +6,16 @@ import ( "github.com/spf13/cobra" ) -var catCmd = &cobra.Command{ - Use: "cat FILE_PATH", - Short: "Show file content", - Long: `Show the contents of a file.`, - Args: cobra.ExactArgs(1), - PreRunE: root.MustWorkspaceClient, +func newCatCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "cat FILE_PATH", + Short: "Show file content", + Long: `Show the contents of a file.`, + Args: cobra.ExactArgs(1), + PreRunE: root.MustWorkspaceClient, + } - RunE: func(cmd *cobra.Command, args []string) error { + cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() f, path, err := filerForPath(ctx, args[0]) @@ -26,9 +28,7 @@ var catCmd = &cobra.Command{ return err } return cmdio.RenderReader(ctx, r) - }, -} + } -func init() { - fsCmd.AddCommand(catCmd) + return cmd } diff --git a/cmd/fs/cp.go b/cmd/fs/cp.go index 204d6c33..294d2dab 100644 --- a/cmd/fs/cp.go +++ b/cmd/fs/cp.go @@ -15,6 +15,9 @@ import ( ) type copy struct { + overwrite bool + recursive bool + ctx context.Context sourceFiler filer.Filer targetFiler filer.Filer @@ -48,7 +51,7 @@ func (c *copy) cpWriteCallback(sourceDir, targetDir string) fs.WalkDirFunc { } func (c *copy) cpDirToDir(sourceDir, targetDir string) error { - if !cpRecursive { + if !c.recursive { return fmt.Errorf("source path %s is a directory. Please specify the --recursive flag", sourceDir) } @@ -71,7 +74,7 @@ func (c *copy) cpFileToFile(sourcePath, targetPath string) error { } defer r.Close() - if cpOverwrite { + if c.overwrite { err = c.targetFiler.Write(c.ctx, targetPath, r, filer.OverwriteIfExists) if err != nil { return err @@ -123,28 +126,30 @@ func (c *copy) emitFileCopiedEvent(sourcePath, targetPath string) error { return cmdio.RenderWithTemplate(c.ctx, event, template) } -var cpOverwrite bool -var cpRecursive bool +func newCpCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "cp SOURCE_PATH TARGET_PATH", + Short: "Copy files and directories to and from DBFS.", + Long: `Copy files to and from DBFS. -// cpCmd represents the fs cp command -var cpCmd = &cobra.Command{ - Use: "cp SOURCE_PATH TARGET_PATH", - Short: "Copy files and directories to and from DBFS.", - Long: `Copy files to and from DBFS. + For paths in DBFS it is required that you specify the "dbfs" scheme. + For example: dbfs:/foo/bar. - For paths in DBFS it is required that you specify the "dbfs" scheme. - For example: dbfs:/foo/bar. + Recursively copying a directory will copy all files inside directory + at SOURCE_PATH to the directory at TARGET_PATH. - Recursively copying a directory will copy all files inside directory - at SOURCE_PATH to the directory at TARGET_PATH. + When copying a file, if TARGET_PATH is a directory, the file will be created + inside the directory, otherwise the file is created at TARGET_PATH. + `, + Args: cobra.ExactArgs(2), + PreRunE: root.MustWorkspaceClient, + } - When copying a file, if TARGET_PATH is a directory, the file will be created - inside the directory, otherwise the file is created at TARGET_PATH. -`, - Args: cobra.ExactArgs(2), - PreRunE: root.MustWorkspaceClient, + var c copy + cmd.Flags().BoolVar(&c.overwrite, "overwrite", false, "overwrite existing files") + cmd.Flags().BoolVarP(&c.recursive, "recursive", "r", false, "recursively copy files from directory") - RunE: func(cmd *cobra.Command, args []string) error { + cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() // TODO: Error if a user uses '\' as path separator on windows when "file" @@ -164,22 +169,18 @@ var cpCmd = &cobra.Command{ return err } - sourceScheme := "" + c.sourceScheme = "" if isDbfsPath(fullSourcePath) { - sourceScheme = "dbfs" + c.sourceScheme = "dbfs" } - targetScheme := "" + c.targetScheme = "" if isDbfsPath(fullTargetPath) { - targetScheme = "dbfs" + c.targetScheme = "dbfs" } - c := copy{ - ctx: ctx, - sourceFiler: sourceFiler, - targetFiler: targetFiler, - sourceScheme: sourceScheme, - targetScheme: targetScheme, - } + c.ctx = ctx + c.sourceFiler = sourceFiler + c.targetFiler = targetFiler // Get information about file at source path sourceInfo, err := sourceFiler.Stat(ctx, sourcePath) @@ -200,11 +201,7 @@ var cpCmd = &cobra.Command{ // case 3: source path is a file, and target path is a file return c.cpFileToFile(sourcePath, targetPath) - }, -} + } -func init() { - cpCmd.Flags().BoolVar(&cpOverwrite, "overwrite", false, "overwrite existing files") - cpCmd.Flags().BoolVarP(&cpRecursive, "recursive", "r", false, "recursively copy files from directory") - fsCmd.AddCommand(cpCmd) + return cmd } diff --git a/cmd/fs/fs.go b/cmd/fs/fs.go index a69c4b62..190220f4 100644 --- a/cmd/fs/fs.go +++ b/cmd/fs/fs.go @@ -1,17 +1,23 @@ package fs import ( - "github.com/databricks/cli/cmd/root" "github.com/spf13/cobra" ) -// fsCmd represents the fs command -var fsCmd = &cobra.Command{ - Use: "fs", - Short: "Filesystem related commands", - Long: `Commands to do DBFS operations.`, -} +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "fs", + Short: "Filesystem related commands", + Long: `Commands to do DBFS operations.`, + } -func init() { - root.RootCmd.AddCommand(fsCmd) + cmd.AddCommand( + newCatCommand(), + newCpCommand(), + newLsCommand(), + newMkdirCommand(), + newRmCommand(), + ) + + return cmd } diff --git a/cmd/fs/ls.go b/cmd/fs/ls.go index b06345d5..7ae55e1f 100644 --- a/cmd/fs/ls.go +++ b/cmd/fs/ls.go @@ -37,15 +37,21 @@ func toJsonDirEntry(f fs.DirEntry, baseDir string, isAbsolute bool) (*jsonDirEnt }, nil } -// lsCmd represents the ls command -var lsCmd = &cobra.Command{ - Use: "ls DIR_PATH", - Short: "Lists files", - Long: `Lists files`, - Args: cobra.ExactArgs(1), - PreRunE: root.MustWorkspaceClient, +func newLsCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "ls DIR_PATH", + Short: "Lists files", + Long: `Lists files`, + Args: cobra.ExactArgs(1), + PreRunE: root.MustWorkspaceClient, + } - RunE: func(cmd *cobra.Command, args []string) error { + var long bool + var absolute bool + cmd.Flags().BoolVarP(&long, "long", "l", false, "Displays full information including size, file type and modification time since Epoch in milliseconds.") + cmd.Flags().BoolVar(&absolute, "absolute", false, "Displays absolute paths.") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() f, path, err := filerForPath(ctx, args[0]) @@ -60,7 +66,7 @@ var lsCmd = &cobra.Command{ jsonDirEntries := make([]jsonDirEntry, len(entries)) for i, entry := range entries { - jsonDirEntry, err := toJsonDirEntry(entry, args[0], lsAbsolute) + jsonDirEntry, err := toJsonDirEntry(entry, args[0], absolute) if err != nil { return err } @@ -71,7 +77,7 @@ var lsCmd = &cobra.Command{ }) // Use template for long mode if the flag is set - if longMode { + if long { return cmdio.RenderWithTemplate(ctx, jsonDirEntries, cmdio.Heredoc(` {{range .}}{{if .IsDir}}DIRECTORY {{else}}FILE {{end}}{{.Size}} {{.ModTime|pretty_date}} {{.Name}} {{end}} @@ -81,14 +87,7 @@ var lsCmd = &cobra.Command{ {{range .}}{{.Name}} {{end}} `)) - }, -} + } -var longMode bool -var lsAbsolute bool - -func init() { - lsCmd.Flags().BoolVarP(&longMode, "long", "l", false, "Displays full information including size, file type and modification time since Epoch in milliseconds.") - lsCmd.Flags().BoolVar(&lsAbsolute, "absolute", false, "Displays absolute paths.") - fsCmd.AddCommand(lsCmd) + return cmd } diff --git a/cmd/fs/mkdir.go b/cmd/fs/mkdir.go index cb049139..c6a5e607 100644 --- a/cmd/fs/mkdir.go +++ b/cmd/fs/mkdir.go @@ -5,17 +5,19 @@ import ( "github.com/spf13/cobra" ) -var mkdirCmd = &cobra.Command{ - Use: "mkdir DIR_PATH", - // Alias `mkdirs` for this command exists for legacy purposes. This command - // is called databricks fs mkdirs in our legacy CLI: https://github.com/databricks/databricks-cli - Aliases: []string{"mkdirs"}, - Short: "Make directories", - Long: `Mkdir will create directories along the path to the argument directory.`, - Args: cobra.ExactArgs(1), - PreRunE: root.MustWorkspaceClient, +func newMkdirCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "mkdir DIR_PATH", + // Alias `mkdirs` for this command exists for legacy purposes. This command + // is called databricks fs mkdirs in our legacy CLI: https://github.com/databricks/databricks-cli + Aliases: []string{"mkdirs"}, + Short: "Make directories", + Long: `Mkdir will create directories along the path to the argument directory.`, + Args: cobra.ExactArgs(1), + PreRunE: root.MustWorkspaceClient, + } - RunE: func(cmd *cobra.Command, args []string) error { + cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() f, path, err := filerForPath(ctx, args[0]) @@ -24,9 +26,7 @@ var mkdirCmd = &cobra.Command{ } return f.Mkdir(ctx, path) - }, -} + } -func init() { - fsCmd.AddCommand(mkdirCmd) + return cmd } diff --git a/cmd/fs/rm.go b/cmd/fs/rm.go index 21f5adb9..3ce8d3b9 100644 --- a/cmd/fs/rm.go +++ b/cmd/fs/rm.go @@ -6,14 +6,19 @@ import ( "github.com/spf13/cobra" ) -var rmCmd = &cobra.Command{ - Use: "rm PATH", - Short: "Remove files and directories from dbfs.", - Long: `Remove files and directories from dbfs.`, - Args: cobra.ExactArgs(1), - PreRunE: root.MustWorkspaceClient, +func newRmCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "rm PATH", + Short: "Remove files and directories from dbfs.", + Long: `Remove files and directories from dbfs.`, + Args: cobra.ExactArgs(1), + PreRunE: root.MustWorkspaceClient, + } - RunE: func(cmd *cobra.Command, args []string) error { + var recursive bool + cmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "Recursively delete a non-empty directory.") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() f, path, err := filerForPath(ctx, args[0]) @@ -25,12 +30,7 @@ var rmCmd = &cobra.Command{ return f.Delete(ctx, path, filer.DeleteRecursively) } return f.Delete(ctx, path) - }, -} + } -var recursive bool - -func init() { - rmCmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "Recursively delete a non-empty directory.") - fsCmd.AddCommand(rmCmd) + return cmd } diff --git a/cmd/root/root.go b/cmd/root/root.go index 45fc27f2..0a18594a 100644 --- a/cmd/root/root.go +++ b/cmd/root/root.go @@ -115,6 +115,3 @@ func Execute(cmd *cobra.Command) { os.Exit(1) } } - -// Keep a global copy until all commands can be initialized. -var RootCmd = New() diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index 51d71ea2..d2aad0c3 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -17,7 +17,15 @@ import ( "github.com/spf13/cobra" ) -func syncOptionsFromBundle(cmd *cobra.Command, args []string, b *bundle.Bundle) (*sync.SyncOptions, error) { +type syncFlags struct { + // project files polling interval + interval time.Duration + full bool + watch bool + output flags.Output +} + +func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, args []string, b *bundle.Bundle) (*sync.SyncOptions, error) { if len(args) > 0 { return nil, fmt.Errorf("SRC and DST are not configurable in the context of a bundle") } @@ -30,8 +38,8 @@ func syncOptionsFromBundle(cmd *cobra.Command, args []string, b *bundle.Bundle) opts := sync.SyncOptions{ LocalPath: b.Config.Path, RemotePath: b.Config.Workspace.FilesPath, - Full: full, - PollInterval: interval, + Full: f.full, + PollInterval: f.interval, SnapshotBasePath: cacheDir, WorkspaceClient: b.WorkspaceClient(), @@ -39,7 +47,7 @@ func syncOptionsFromBundle(cmd *cobra.Command, args []string, b *bundle.Bundle) return &opts, nil } -func syncOptionsFromArgs(cmd *cobra.Command, args []string) (*sync.SyncOptions, error) { +func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*sync.SyncOptions, error) { if len(args) != 2 { return nil, flag.ErrHelp } @@ -47,8 +55,8 @@ func syncOptionsFromArgs(cmd *cobra.Command, args []string) (*sync.SyncOptions, opts := sync.SyncOptions{ LocalPath: args[0], RemotePath: args[1], - Full: full, - PollInterval: interval, + Full: f.full, + PollInterval: f.interval, // We keep existing behavior for VS Code extension where if there is // no bundle defined, we store the snapshots in `.databricks`. @@ -60,13 +68,22 @@ func syncOptionsFromArgs(cmd *cobra.Command, args []string) (*sync.SyncOptions, return &opts, nil } -var syncCmd = &cobra.Command{ - Use: "sync [flags] SRC DST", - Short: "Synchronize a local directory to a workspace directory", - Args: cobra.MaximumNArgs(2), +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "sync [flags] SRC DST", + Short: "Synchronize a local directory to a workspace directory", + Args: cobra.MaximumNArgs(2), + } - // PreRunE: root.TryConfigureBundle, - RunE: func(cmd *cobra.Command, args []string) error { + f := syncFlags{ + output: flags.OutputText, + } + cmd.Flags().DurationVar(&f.interval, "interval", 1*time.Second, "file system polling interval (for --watch)") + cmd.Flags().BoolVar(&f.full, "full", false, "perform full synchronization (default is incremental)") + cmd.Flags().BoolVar(&f.watch, "watch", false, "watch local file system for changes") + cmd.Flags().Var(&f.output, "output", "type of output format") + + cmd.RunE = func(cmd *cobra.Command, args []string) error { var opts *sync.SyncOptions var err error @@ -84,7 +101,7 @@ var syncCmd = &cobra.Command{ // } // opts, err = syncOptionsFromBundle(cmd, args, b) // } else { - opts, err = syncOptionsFromArgs(cmd, args) + opts, err = f.syncOptionsFromArgs(cmd, args) // } if err != nil { return err @@ -97,7 +114,7 @@ var syncCmd = &cobra.Command{ } var outputFunc func(context.Context, <-chan sync.Event, io.Writer) - switch output { + switch f.output { case flags.OutputText: outputFunc = textOutput case flags.OutputJSON: @@ -113,7 +130,7 @@ var syncCmd = &cobra.Command{ }() } - if watch { + if f.watch { err = s.RunContinuous(ctx) } else { err = s.RunOnce(ctx) @@ -122,9 +139,9 @@ var syncCmd = &cobra.Command{ s.Close() wg.Wait() return err - }, + } - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := root.TryConfigureBundle(cmd, args) if err != nil { return nil, cobra.ShellCompDirectiveError @@ -149,19 +166,7 @@ var syncCmd = &cobra.Command{ default: return nil, cobra.ShellCompDirectiveNoFileComp } - }, -} + } -// project files polling interval -var interval time.Duration -var full bool -var watch bool -var output flags.Output = flags.OutputText - -func init() { - root.RootCmd.AddCommand(syncCmd) - syncCmd.Flags().DurationVar(&interval, "interval", 1*time.Second, "file system polling interval (for --watch)") - syncCmd.Flags().BoolVar(&full, "full", false, "perform full synchronization (default is incremental)") - syncCmd.Flags().BoolVar(&watch, "watch", false, "watch local file system for changes") - syncCmd.Flags().Var(&output, "output", "type of output format") + return cmd } diff --git a/cmd/sync/sync_test.go b/cmd/sync/sync_test.go index 2d8c8b11..a6eedbe6 100644 --- a/cmd/sync/sync_test.go +++ b/cmd/sync/sync_test.go @@ -27,7 +27,8 @@ func TestSyncOptionsFromBundle(t *testing.T) { }, } - opts, err := syncOptionsFromBundle(syncCmd, []string{}, b) + f := syncFlags{} + opts, err := f.syncOptionsFromBundle(New(), []string{}, b) require.NoError(t, err) assert.Equal(t, tempDir, opts.LocalPath) assert.Equal(t, "/Users/jane@doe.com/path", opts.RemotePath) @@ -37,16 +38,18 @@ func TestSyncOptionsFromBundle(t *testing.T) { func TestSyncOptionsFromArgsRequiredTwoArgs(t *testing.T) { var err error - _, err = syncOptionsFromArgs(syncCmd, []string{}) + f := syncFlags{} + _, err = f.syncOptionsFromArgs(New(), []string{}) require.ErrorIs(t, err, flag.ErrHelp) - _, err = syncOptionsFromArgs(syncCmd, []string{"foo"}) + _, err = f.syncOptionsFromArgs(New(), []string{"foo"}) require.ErrorIs(t, err, flag.ErrHelp) - _, err = syncOptionsFromArgs(syncCmd, []string{"foo", "bar", "qux"}) + _, err = f.syncOptionsFromArgs(New(), []string{"foo", "bar", "qux"}) require.ErrorIs(t, err, flag.ErrHelp) } func TestSyncOptionsFromArgs(t *testing.T) { - opts, err := syncOptionsFromArgs(syncCmd, []string{"/local", "/remote"}) + f := syncFlags{} + opts, err := f.syncOptionsFromArgs(New(), []string{"/local", "/remote"}) require.NoError(t, err) assert.Equal(t, "/local", opts.LocalPath) assert.Equal(t, "/remote", opts.RemotePath) diff --git a/cmd/version/version.go b/cmd/version/version.go index 1f772424..17bb4b9a 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -1,25 +1,24 @@ package version import ( - "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/internal/build" "github.com/databricks/cli/libs/cmdio" "github.com/spf13/cobra" ) -var versionCmd = &cobra.Command{ - Use: "version", - Args: cobra.NoArgs, +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "version", + Args: cobra.NoArgs, - Annotations: map[string]string{ - "template": "Databricks CLI v{{.Version}}\n", - }, + Annotations: map[string]string{ + "template": "Databricks CLI v{{.Version}}\n", + }, + } - RunE: func(cmd *cobra.Command, args []string) error { + cmd.RunE = func(cmd *cobra.Command, args []string) error { return cmdio.Render(cmd.Context(), build.GetInfo()) - }, -} + } -func init() { - root.RootCmd.AddCommand(versionCmd) + return cmd } diff --git a/internal/secrets_test.go b/internal/secrets_test.go index 1e9c86ab..b030071b 100644 --- a/internal/secrets_test.go +++ b/internal/secrets_test.go @@ -77,13 +77,6 @@ func TestSecretsPutSecretStringValue(tt *testing.T) { func TestSecretsPutSecretBytesValue(tt *testing.T) { ctx, t := acc.WorkspaceTest(tt) - - if true { - // Uncomment below to run this test in isolation. - // To be addressed once none of the commands taint global state. - t.Skip("skipping because the test above clobbers global state") - } - scope := temporarySecretScope(ctx, t) key := "test-key" value := []byte{0x00, 0x01, 0x02, 0x03} diff --git a/main.go b/main.go index 414e42d0..a4b8aabd 100644 --- a/main.go +++ b/main.go @@ -2,17 +2,7 @@ package main import ( "github.com/databricks/cli/cmd" - _ "github.com/databricks/cli/cmd/account" - _ "github.com/databricks/cli/cmd/api" - _ "github.com/databricks/cli/cmd/auth" - _ "github.com/databricks/cli/cmd/bundle" - _ "github.com/databricks/cli/cmd/bundle/debug" - _ "github.com/databricks/cli/cmd/configure" - _ "github.com/databricks/cli/cmd/fs" "github.com/databricks/cli/cmd/root" - _ "github.com/databricks/cli/cmd/sync" - _ "github.com/databricks/cli/cmd/version" - _ "github.com/databricks/cli/cmd/workspace" ) func main() {