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() {