src/server/cmd/pachctl/cmd/cmd.go
func PachctlCmd() *cobra.Command {
	var verbose bool
	var noMetrics bool
	var noPortForwarding bool

	raw := false
	rawFlags := pflag.NewFlagSet("", pflag.ContinueOnError)
	rawFlags.BoolVar(&raw, "raw", false, "disable pretty printing, print raw json")

	marshaller := &jsonpb.Marshaler{Indent: "  "}

	rootCmd := &cobra.Command{
		Use: os.Args[0],
		Long: `Access the Pachyderm API.

Environment variables:
  PACHD_ADDRESS=<host>:<port>, the pachd server to connect to (e.g. 127.0.0.1:30650).
  PACH_CONFIG=<path>, the path where pachctl will attempt to load your pach config.
  JAEGER_ENDPOINT=<host>:<port>, the Jaeger server to connect to, if PACH_ENABLE_TRACING is set
  PACH_ENABLE_TRACING={true,false}, If true, and JAEGER_ENDPOINT is set, attach a
    Jaeger trace to all outgoing RPCs
`,
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			log.SetFormatter(new(prefixed.TextFormatter))

			if !verbose {
				log.SetLevel(log.ErrorLevel)
				// Silence grpc logs
				grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard))
			} else {
				log.SetLevel(log.DebugLevel)
				// etcd overrides grpc's logs--there's no way to enable one without
				// enabling both.
				// Error and warning logs are discarded because they will be
				// redundantly sent to the info logger. See:
				// https://godoc.org/google.golang.org/grpc/grpclog#NewLoggerV2
				logger := log.StandardLogger()
				etcd.SetLogger(grpclog.NewLoggerV2(
					logutil.NewGRPCLogWriter(logger, "etcd/grpc"),
					ioutil.Discard,
					ioutil.Discard,
				))
			}
		},
		BashCompletionFunction: bashCompletionFunc,
	}
	rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Output verbose logs")
	rootCmd.PersistentFlags().BoolVarP(&noMetrics, "no-metrics", "", false, "Don't report user metrics for this command")
	rootCmd.PersistentFlags().BoolVarP(&noPortForwarding, "no-port-forwarding", "", false, "Disable implicit port forwarding")

	var subcommands []*cobra.Command

	var clientOnly bool
	var timeoutFlag string
	versionCmd := &cobra.Command{
		Short: "Print Pachyderm version information.",
		Long:  "Print Pachyderm version information.",
		Run: cmdutil.RunFixedArgs(0, func(args []string) (retErr error) {
			if clientOnly {
				if raw {
					if err := marshaller.Marshal(os.Stdout, version.Version); err != nil {
						return err
					}
				} else {
					fmt.Println(version.PrettyPrintVersion(version.Version))
				}
				return nil
			}
			if !noMetrics {
				start := time.Now()
				startMetricsWait := metrics.StartReportAndFlushUserAction("Version", start)
				defer startMetricsWait()
				defer func() {
					finishMetricsWait := metrics.FinishReportAndFlushUserAction("Version", retErr, start)
					finishMetricsWait()
				}()
			}

			// Print header + client version
			writer := ansiterm.NewTabWriter(os.Stdout, 20, 1, 3, ' ', 0)
			if raw {
				if err := marshaller.Marshal(os.Stdout, version.Version); err != nil {
					return err
				}
			} else {
				printVersionHeader(writer)
				printVersion(writer, "pachctl", version.Version)
				if err := writer.Flush(); err != nil {
					return err
				}
			}

			// Dial pachd & get server version
			var pachClient *client.APIClient
			var err error
			if timeoutFlag != "default" {
				var timeout time.Duration
				timeout, err = time.ParseDuration(timeoutFlag)
				if err != nil {
					return fmt.Errorf("could not parse timeout duration %q: %v", timeout, err)
				}
				pachClient, err = client.NewOnUserMachine(false, !noPortForwarding, "user", client.WithDialTimeout(timeout))
			} else {
				pachClient, err = client.NewOnUserMachine(false, !noPortForwarding, "user")
			}
			if err != nil {
				return err
			}
			defer pachClient.Close()
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()
			version, err := pachClient.GetVersion(ctx, &types.Empty{})

			if err != nil {
				buf := bytes.NewBufferString("")
				errWriter := ansiterm.NewTabWriter(buf, 20, 1, 3, ' ', 0)
				fmt.Fprintf(errWriter, "pachd\t(version unknown) : error connecting to pachd server at address (%v): %v\n\nplease make sure pachd is up (`kubectl get all`) and portforwarding is enabled\n", pachClient.GetAddress(), grpc.ErrorDesc(err))
				errWriter.Flush()
				return errors.New(buf.String())
			}

			// print server version
			if raw {
				if err := marshaller.Marshal(os.Stdout, version); err != nil {
					return err
				}
			} else {
				printVersion(writer, "pachd", version)
				if err := writer.Flush(); err != nil {
					return err
				}
			}
			return nil
		}),
	}
	versionCmd.Flags().BoolVar(&clientOnly, "client-only", false, "If set, "+
		"only print pachctl's version, but don't make any RPCs to pachd. Useful "+
		"if pachd is unavailable")
	versionCmd.Flags().StringVar(&timeoutFlag, "timeout", "default", "If set, "+
		"'pachctl version' will timeout after the given duration (formatted as a "+
		"golang time duration--a number followed by ns, us, ms, s, m, or h). If "+
		"--client-only is set, this flag is ignored. If unset, pachctl will use a "+
		"default timeout; if set to 0s, the call will never time out.")
	versionCmd.Flags().AddFlagSet(rawFlags)
	subcommands = append(subcommands, cmdutil.CreateAlias(versionCmd, "version"))

	deleteAll := &cobra.Command{
		Short: "Delete everything.",
		Long: `Delete all repos, commits, files, pipelines and jobs.
This resets the cluster to its initial state.`,
		Run: cmdutil.RunFixedArgs(0, func(args []string) error {
			client, err := client.NewOnUserMachine(!noMetrics, !noPortForwarding, "user")
			if err != nil {
				return err
			}
			defer client.Close()
			red := color.New(color.FgRed).SprintFunc()
			var repos, pipelines []string
			repoInfos, err := client.ListRepo()
			if err != nil {
				return err
			}
			for _, ri := range repoInfos {
				repos = append(repos, red(ri.Repo.Name))
			}
			pipelineInfos, err := client.ListPipeline()
			if err != nil {
				return err
			}
			for _, pi := range pipelineInfos {
				pipelines = append(pipelines, red(pi.Pipeline.Name))
			}
			fmt.Println("All ACLs, repos, commits, files, pipelines and jobs will be deleted.")
			if len(repos) > 0 {
				fmt.Printf("Repos to delete: %s\n", strings.Join(repos, ", "))
			}
			if len(pipelines) > 0 {
				fmt.Printf("Pipelines to delete: %s\n", strings.Join(pipelines, ", "))
			}
			fmt.Println("Are you sure you want to do this? (y/n):")
			r := bufio.NewReader(os.Stdin)
			bytes, err := r.ReadBytes('\n')
			if err != nil {
				return err
			}
			if bytes[0] == 'y' || bytes[0] == 'Y' {
				return client.DeleteAll()
			}
			return nil
		}),
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(deleteAll, "delete all"))

	var port uint16
	var remotePort uint16
	var samlPort uint16
	var uiPort uint16
	var uiWebsocketPort uint16
	var pfsPort uint16
	var s3gatewayPort uint16
	var namespace string
	portForward := &cobra.Command{
		Short: "Forward a port on the local machine to pachd. This command blocks.",
		Long:  "Forward a port on the local machine to pachd. This command blocks.",
		Run: cmdutil.RunFixedArgs(0, func(args []string) error {
			fw, err := client.NewPortForwarder(namespace)
			if err != nil {
				return err
			}

			if err = fw.Lock(); err != nil {
				return err
			}

			defer fw.Close()

			failCount := 0

			fmt.Println("Forwarding the pachd (Pachyderm daemon) port...")
			if err = fw.RunForDaemon(port, remotePort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			fmt.Println("Forwarding the SAML ACS port...")
			if err = fw.RunForSAMLACS(samlPort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			fmt.Printf("Forwarding the dash (Pachyderm dashboard) UI port to http://localhost:%v...\n", uiPort)
			if err = fw.RunForDashUI(uiPort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			fmt.Println("Forwarding the dash (Pachyderm dashboard) websocket port...")
			if err = fw.RunForDashWebSocket(uiWebsocketPort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			fmt.Println("Forwarding the PFS port...")
			if err = fw.RunForPFS(pfsPort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			fmt.Println("Forwarding the s3gateway port...")
			if err = fw.RunForS3Gateway(s3gatewayPort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			if failCount < 6 {
				fmt.Println("CTRL-C to exit")
				ch := make(chan os.Signal, 1)
				signal.Notify(ch, os.Interrupt)
				<-ch
			}

			return nil
		}),
	}
	portForward.Flags().Uint16VarP(&port, "port", "p", 30650, "The local port to bind pachd to.")
	portForward.Flags().Uint16Var(&remotePort, "remote-port", 650, "The remote port that pachd is bound to in the cluster.")
	portForward.Flags().Uint16Var(&samlPort, "saml-port", 30654, "The local port to bind pachd's SAML ACS to.")
	portForward.Flags().Uint16VarP(&uiPort, "ui-port", "u", 30080, "The local port to bind Pachyderm's dash service to.")
	portForward.Flags().Uint16VarP(&uiWebsocketPort, "proxy-port", "x", 30081, "The local port to bind Pachyderm's dash proxy service to.")
	portForward.Flags().Uint16VarP(&pfsPort, "pfs-port", "f", 30652, "The local port to bind PFS over HTTP to.")
	portForward.Flags().Uint16VarP(&s3gatewayPort, "s3gateway-port", "s", 30600, "The local port to bind the s3gateway to.")
	portForward.Flags().StringVar(&namespace, "namespace", "default", "Kubernetes namespace Pachyderm is deployed in.")
	subcommands = append(subcommands, cmdutil.CreateAlias(portForward, "port-forward"))

	var install bool
	var path string
	completion := &cobra.Command{
		Short: "Print or install the bash completion code.",
		Long:  "Print or install the bash completion code. This should be placed as the file `pachctl` in the bash completion directory (by default this is `/etc/bash_completion.d`. If bash-completion was installed via homebrew, this would be `$(brew --prefix)/etc/bash_completion.d`.)",
		Run: cmdutil.RunFixedArgs(0, func(args []string) (retErr error) {
			var dest io.Writer

			if install {
				f, err := os.Create(path)

				if err != nil {
					if os.IsPermission(err) {
						fmt.Fprintf(os.Stderr, "Permission error installing completions, rerun this command with sudo.\n")
					}
					return err
				}

				defer func() {
					if err := f.Close(); err != nil && retErr == nil {
						retErr = err
					}

					fmt.Printf("Bash completions installed in %s, you must restart bash to enable completions.\n", path)
				}()

				dest = f
			} else {
				dest = os.Stdout
			}

			// Remove 'hidden' flag from all commands so we can get bash completions for them as well
			var unhide func(*cobra.Command)
			unhide = func(cmd *cobra.Command) {
				cmd.Hidden = false
				for _, subcmd := range cmd.Commands() {
					unhide(subcmd)
				}
			}
			unhide(rootCmd)

			return rootCmd.GenBashCompletion(dest)
		}),
	}
	completion.Flags().BoolVar(&install, "install", false, "Install the completion.")
	completion.Flags().StringVar(&path, "path", "/etc/bash_completion.d/pachctl", "Path to install the completion to. This will default to `/etc/bash_completion.d/` if unspecified.")
	subcommands = append(subcommands, cmdutil.CreateAlias(completion, "completion"))

	// Logical commands for grouping commands by verb (no run functions)
	deleteDocs := &cobra.Command{
		Short: "Delete an existing Pachyderm resource.",
		Long:  "Delete an existing Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(deleteDocs, "delete"))

	createDocs := &cobra.Command{
		Short: "Create a new instance of a Pachyderm resource.",
		Long:  "Create a new instance of a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(createDocs, "create"))

	updateDocs := &cobra.Command{
		Short: "Change the properties of an existing Pachyderm resource.",
		Long:  "Change the properties of an existing Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(updateDocs, "update"))

	inspectDocs := &cobra.Command{
		Short: "Show detailed information about a Pachyderm resource.",
		Long:  "Show detailed information about a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(inspectDocs, "inspect"))

	listDocs := &cobra.Command{
		Short: "Print a list of Pachyderm resources of a specific type.",
		Long:  "Print a list of Pachyderm resources of a specific type.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(listDocs, "list"))

	startDocs := &cobra.Command{
		Short: "Start a Pachyderm resource.",
		Long:  "Start a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(startDocs, "start"))

	finishDocs := &cobra.Command{
		Short: "Finish a Pachyderm resource.",
		Long:  "Finish a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(finishDocs, "finish"))

	flushDocs := &cobra.Command{
		Short: "Wait for the side-effects of a Pachyderm resource to propagate.",
		Long:  "Wait for the side-effects of a Pachyderm resource to propagate.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(flushDocs, "flush"))

	subscribeDocs := &cobra.Command{
		Short: "Wait for notifications of changes to a Pachyderm resource.",
		Long:  "Wait for notifications of changes to a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(subscribeDocs, "subscribe"))

	putDocs := &cobra.Command{
		Short: "Insert data into Pachyderm.",
		Long:  "Insert data into Pachyderm.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(putDocs, "put"))

	copyDocs := &cobra.Command{
		Short: "Copy a Pachyderm resource.",
		Long:  "Copy a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(copyDocs, "copy"))

	getDocs := &cobra.Command{
		Short: "Get the raw data represented by a Pachyderm resource.",
		Long:  "Get the raw data represented by a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(getDocs, "get"))

	globDocs := &cobra.Command{
		Short: "Print a list of Pachyderm resources matching a glob pattern.",
		Long:  "Print a list of Pachyderm resources matching a glob pattern.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(globDocs, "glob"))

	diffDocs := &cobra.Command{
		Short: "Show the differences between two Pachyderm resources.",
		Long:  "Show the differences between two Pachyderm resources.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(diffDocs, "diff"))

	stopDocs := &cobra.Command{
		Short: "Cancel an ongoing task.",
		Long:  "Cancel an ongoing task.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(stopDocs, "stop"))

	restartDocs := &cobra.Command{
		Short: "Cancel and restart an ongoing task.",
		Long:  "Cancel and restart an ongoing task.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(restartDocs, "restart"))

	editDocs := &cobra.Command{
		Short: "Edit the value of an existing Pachyderm resource.",
		Long:  "Edit the value of an existing Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(editDocs, "edit"))

	subcommands = append(subcommands, pfscmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, ppscmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, deploycmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, authcmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, enterprisecmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, admincmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, debugcmds.Cmds(&noMetrics, &noPortForwarding)...)

	cmdutil.MergeCommands(rootCmd, subcommands)

	applyRootUsageFunc(rootCmd)
	applyCommandCompat1_8(rootCmd, &noMetrics, &noPortForwarding)

	return rootCmd
}
func PachctlCmd() *cobra.Command {
	var verbose bool
	var noMetrics bool
	var noPortForwarding bool

	raw := false
	rawFlags := pflag.NewFlagSet("", pflag.ContinueOnError)
	rawFlags.BoolVar(&raw, "raw", false, "disable pretty printing, print raw json")

	marshaller := &jsonpb.Marshaler{Indent: "  "}

	rootCmd := &cobra.Command{
		Use: os.Args[0],
		Long: `Access the Pachyderm API.

Environment variables:
  PACHD_ADDRESS=<host>:<port>, the pachd server to connect to (e.g. 127.0.0.1:30650).
  PACH_CONFIG=<path>, the path where pachctl will attempt to load your pach config.
  JAEGER_ENDPOINT=<host>:<port>, the Jaeger server to connect to, if PACH_ENABLE_TRACING is set
  PACH_ENABLE_TRACING={true,false}, If true, and JAEGER_ENDPOINT is set, attach a
    Jaeger trace to all outgoing RPCs
`,
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			log.SetFormatter(new(prefixed.TextFormatter))

			if !verbose {
				log.SetLevel(log.ErrorLevel)
				// Silence grpc logs
				grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard))
			} else {
				log.SetLevel(log.DebugLevel)
				// etcd overrides grpc's logs--there's no way to enable one without
				// enabling both.
				// Error and warning logs are discarded because they will be
				// redundantly sent to the info logger. See:
				// https://godoc.org/google.golang.org/grpc/grpclog#NewLoggerV2
				logger := log.StandardLogger()
				etcd.SetLogger(grpclog.NewLoggerV2(
					logutil.NewGRPCLogWriter(logger, "etcd/grpc"),
					ioutil.Discard,
					ioutil.Discard,
				))
			}
		},
		BashCompletionFunction: bashCompletionFunc,
	}
	rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Output verbose logs")
	rootCmd.PersistentFlags().BoolVarP(&noMetrics, "no-metrics", "", false, "Don't report user metrics for this command")
	rootCmd.PersistentFlags().BoolVarP(&noPortForwarding, "no-port-forwarding", "", false, "Disable implicit port forwarding")

	var subcommands []*cobra.Command

	var clientOnly bool
	var timeoutFlag string
	versionCmd := &cobra.Command{
		Short: "Print Pachyderm version information.",
		Long:  "Print Pachyderm version information.",
		Run: cmdutil.RunFixedArgs(0, func(args []string) (retErr error) {
			if clientOnly {
				if raw {
					if err := marshaller.Marshal(os.Stdout, version.Version); err != nil {
						return err
					}
				} else {
					fmt.Println(version.PrettyPrintVersion(version.Version))
				}
				return nil
			}
			if !noMetrics {
				start := time.Now()
				startMetricsWait := metrics.StartReportAndFlushUserAction("Version", start)
				defer startMetricsWait()
				defer func() {
					finishMetricsWait := metrics.FinishReportAndFlushUserAction("Version", retErr, start)
					finishMetricsWait()
				}()
			}

			// Print header + client version
			writer := ansiterm.NewTabWriter(os.Stdout, 20, 1, 3, ' ', 0)
			if raw {
				if err := marshaller.Marshal(os.Stdout, version.Version); err != nil {
					return err
				}
			} else {
				printVersionHeader(writer)
				printVersion(writer, "pachctl", version.Version)
				if err := writer.Flush(); err != nil {
					return err
				}
			}

			// Dial pachd & get server version
			var pachClient *client.APIClient
			var err error
			if timeoutFlag != "default" {
				var timeout time.Duration
				timeout, err = time.ParseDuration(timeoutFlag)
				if err != nil {
					return fmt.Errorf("could not parse timeout duration %q: %v", timeout, err)
				}
				pachClient, err = client.NewOnUserMachine(false, !noPortForwarding, "user", client.WithDialTimeout(timeout))
			} else {
				pachClient, err = client.NewOnUserMachine(false, !noPortForwarding, "user")
			}
			if err != nil {
				return err
			}
			defer pachClient.Close()
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()
			version, err := pachClient.GetVersion(ctx, &types.Empty{})

			if err != nil {
				buf := bytes.NewBufferString("")
				errWriter := ansiterm.NewTabWriter(buf, 20, 1, 3, ' ', 0)
				fmt.Fprintf(errWriter, "pachd\t(version unknown) : error connecting to pachd server at address (%v): %v\n\nplease make sure pachd is up (`kubectl get all`) and portforwarding is enabled\n", pachClient.GetAddress(), grpc.ErrorDesc(err))
				errWriter.Flush()
				return errors.New(buf.String())
			}

			// print server version
			if raw {
				if err := marshaller.Marshal(os.Stdout, version); err != nil {
					return err
				}
			} else {
				printVersion(writer, "pachd", version)
				if err := writer.Flush(); err != nil {
					return err
				}
			}
			return nil
		}),
	}
	versionCmd.Flags().BoolVar(&clientOnly, "client-only", false, "If set, "+
		"only print pachctl's version, but don't make any RPCs to pachd. Useful "+
		"if pachd is unavailable")
	versionCmd.Flags().StringVar(&timeoutFlag, "timeout", "default", "If set, "+
		"'pachctl version' will timeout after the given duration (formatted as a "+
		"golang time duration--a number followed by ns, us, ms, s, m, or h). If "+
		"--client-only is set, this flag is ignored. If unset, pachctl will use a "+
		"default timeout; if set to 0s, the call will never time out.")
	versionCmd.Flags().AddFlagSet(rawFlags)
	subcommands = append(subcommands, cmdutil.CreateAlias(versionCmd, "version"))

	deleteAll := &cobra.Command{
		Short: "Delete everything.",
		Long: `Delete all repos, commits, files, pipelines and jobs.
This resets the cluster to its initial state.`,
		Run: cmdutil.RunFixedArgs(0, func(args []string) error {
			client, err := client.NewOnUserMachine(!noMetrics, !noPortForwarding, "user")
			if err != nil {
				return err
			}
			defer client.Close()
			red := color.New(color.FgRed).SprintFunc()
			var repos, pipelines []string
			repoInfos, err := client.ListRepo()
			if err != nil {
				return err
			}
			for _, ri := range repoInfos {
				repos = append(repos, red(ri.Repo.Name))
			}
			pipelineInfos, err := client.ListPipeline()
			if err != nil {
				return err
			}
			for _, pi := range pipelineInfos {
				pipelines = append(pipelines, red(pi.Pipeline.Name))
			}
			fmt.Println("All ACLs, repos, commits, files, pipelines and jobs will be deleted.")
			if len(repos) > 0 {
				fmt.Printf("Repos to delete: %s\n", strings.Join(repos, ", "))
			}
			if len(pipelines) > 0 {
				fmt.Printf("Pipelines to delete: %s\n", strings.Join(pipelines, ", "))
			}
			fmt.Println("Are you sure you want to do this? (y/n):")
			r := bufio.NewReader(os.Stdin)
			bytes, err := r.ReadBytes('\n')
			if err != nil {
				return err
			}
			if bytes[0] == 'y' || bytes[0] == 'Y' {
				return client.DeleteAll()
			}
			return nil
		}),
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(deleteAll, "delete all"))

	var port uint16
	var remotePort uint16
	var samlPort uint16
	var uiPort uint16
	var uiWebsocketPort uint16
	var pfsPort uint16
	var s3gatewayPort uint16
	var namespace string
	portForward := &cobra.Command{
		Short: "Forward a port on the local machine to pachd. This command blocks.",
		Long:  "Forward a port on the local machine to pachd. This command blocks.",
		Run: cmdutil.RunFixedArgs(0, func(args []string) error {
			fw, err := client.NewPortForwarder(namespace)
			if err != nil {
				return err
			}

			if err = fw.Lock(); err != nil {
				return err
			}

			defer fw.Close()

			failCount := 0

			fmt.Println("Forwarding the pachd (Pachyderm daemon) port...")
			if err = fw.RunForDaemon(port, remotePort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			fmt.Println("Forwarding the SAML ACS port...")
			if err = fw.RunForSAMLACS(samlPort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			fmt.Printf("Forwarding the dash (Pachyderm dashboard) UI port to http://localhost:%v...\n", uiPort)
			if err = fw.RunForDashUI(uiPort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			fmt.Println("Forwarding the dash (Pachyderm dashboard) websocket port...")
			if err = fw.RunForDashWebSocket(uiWebsocketPort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			fmt.Println("Forwarding the PFS port...")
			if err = fw.RunForPFS(pfsPort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			fmt.Println("Forwarding the s3gateway port...")
			if err = fw.RunForS3Gateway(s3gatewayPort); err != nil {
				fmt.Printf("%v\n", err)
				failCount++
			}

			if failCount < 6 {
				fmt.Println("CTRL-C to exit")
				ch := make(chan os.Signal, 1)
				signal.Notify(ch, os.Interrupt)
				<-ch
			}

			return nil
		}),
	}
	portForward.Flags().Uint16VarP(&port, "port", "p", 30650, "The local port to bind pachd to.")
	portForward.Flags().Uint16Var(&remotePort, "remote-port", 650, "The remote port that pachd is bound to in the cluster.")
	portForward.Flags().Uint16Var(&samlPort, "saml-port", 30654, "The local port to bind pachd's SAML ACS to.")
	portForward.Flags().Uint16VarP(&uiPort, "ui-port", "u", 30080, "The local port to bind Pachyderm's dash service to.")
	portForward.Flags().Uint16VarP(&uiWebsocketPort, "proxy-port", "x", 30081, "The local port to bind Pachyderm's dash proxy service to.")
	portForward.Flags().Uint16VarP(&pfsPort, "pfs-port", "f", 30652, "The local port to bind PFS over HTTP to.")
	portForward.Flags().Uint16VarP(&s3gatewayPort, "s3gateway-port", "s", 30600, "The local port to bind the s3gateway to.")
	portForward.Flags().StringVar(&namespace, "namespace", "default", "Kubernetes namespace Pachyderm is deployed in.")
	subcommands = append(subcommands, cmdutil.CreateAlias(portForward, "port-forward"))

	var install bool
	var path string
	completion := &cobra.Command{
		Short: "Print or install the bash completion code.",
		Long:  "Print or install the bash completion code. This should be placed as the file `pachctl` in the bash completion directory (by default this is `/etc/bash_completion.d`. If bash-completion was installed via homebrew, this would be `$(brew --prefix)/etc/bash_completion.d`.)",
		Run: cmdutil.RunFixedArgs(0, func(args []string) (retErr error) {
			var dest io.Writer

			if install {
				f, err := os.Create(path)

				if err != nil {
					if os.IsPermission(err) {
						fmt.Fprintf(os.Stderr, "Permission error installing completions, rerun this command with sudo.\n")
					}
					return err
				}

				defer func() {
					if err := f.Close(); err != nil && retErr == nil {
						retErr = err
					}

					fmt.Printf("Bash completions installed in %s, you must restart bash to enable completions.\n", path)
				}()

				dest = f
			} else {
				dest = os.Stdout
			}

			// Remove 'hidden' flag from all commands so we can get bash completions for them as well
			var unhide func(*cobra.Command)
			unhide = func(cmd *cobra.Command) {
				cmd.Hidden = false
				for _, subcmd := range cmd.Commands() {
					unhide(subcmd)
				}
			}
			unhide(rootCmd)

			return rootCmd.GenBashCompletion(dest)
		}),
	}
	completion.Flags().BoolVar(&install, "install", false, "Install the completion.")
	completion.Flags().StringVar(&path, "path", "/etc/bash_completion.d/pachctl", "Path to install the completion to. This will default to `/etc/bash_completion.d/` if unspecified.")
	subcommands = append(subcommands, cmdutil.CreateAlias(completion, "completion"))

	// Logical commands for grouping commands by verb (no run functions)
	deleteDocs := &cobra.Command{
		Short: "Delete an existing Pachyderm resource.",
		Long:  "Delete an existing Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(deleteDocs, "delete"))

	createDocs := &cobra.Command{
		Short: "Create a new instance of a Pachyderm resource.",
		Long:  "Create a new instance of a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(createDocs, "create"))

	updateDocs := &cobra.Command{
		Short: "Change the properties of an existing Pachyderm resource.",
		Long:  "Change the properties of an existing Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(updateDocs, "update"))

	inspectDocs := &cobra.Command{
		Short: "Show detailed information about a Pachyderm resource.",
		Long:  "Show detailed information about a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(inspectDocs, "inspect"))

	listDocs := &cobra.Command{
		Short: "Print a list of Pachyderm resources of a specific type.",
		Long:  "Print a list of Pachyderm resources of a specific type.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(listDocs, "list"))

	startDocs := &cobra.Command{
		Short: "Start a Pachyderm resource.",
		Long:  "Start a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(startDocs, "start"))

	finishDocs := &cobra.Command{
		Short: "Finish a Pachyderm resource.",
		Long:  "Finish a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(finishDocs, "finish"))

	flushDocs := &cobra.Command{
		Short: "Wait for the side-effects of a Pachyderm resource to propagate.",
		Long:  "Wait for the side-effects of a Pachyderm resource to propagate.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(flushDocs, "flush"))

	subscribeDocs := &cobra.Command{
		Short: "Wait for notifications of changes to a Pachyderm resource.",
		Long:  "Wait for notifications of changes to a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(subscribeDocs, "subscribe"))

	putDocs := &cobra.Command{
		Short: "Insert data into Pachyderm.",
		Long:  "Insert data into Pachyderm.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(putDocs, "put"))

	copyDocs := &cobra.Command{
		Short: "Copy a Pachyderm resource.",
		Long:  "Copy a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(copyDocs, "copy"))

	getDocs := &cobra.Command{
		Short: "Get the raw data represented by a Pachyderm resource.",
		Long:  "Get the raw data represented by a Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(getDocs, "get"))

	globDocs := &cobra.Command{
		Short: "Print a list of Pachyderm resources matching a glob pattern.",
		Long:  "Print a list of Pachyderm resources matching a glob pattern.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(globDocs, "glob"))

	diffDocs := &cobra.Command{
		Short: "Show the differences between two Pachyderm resources.",
		Long:  "Show the differences between two Pachyderm resources.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(diffDocs, "diff"))

	stopDocs := &cobra.Command{
		Short: "Cancel an ongoing task.",
		Long:  "Cancel an ongoing task.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(stopDocs, "stop"))

	restartDocs := &cobra.Command{
		Short: "Cancel and restart an ongoing task.",
		Long:  "Cancel and restart an ongoing task.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(restartDocs, "restart"))

	editDocs := &cobra.Command{
		Short: "Edit the value of an existing Pachyderm resource.",
		Long:  "Edit the value of an existing Pachyderm resource.",
	}
	subcommands = append(subcommands, cmdutil.CreateAlias(editDocs, "edit"))

	subcommands = append(subcommands, pfscmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, ppscmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, deploycmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, authcmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, enterprisecmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, admincmds.Cmds(&noMetrics, &noPortForwarding)...)
	subcommands = append(subcommands, debugcmds.Cmds(&noMetrics, &noPortForwarding)...)

	cmdutil.MergeCommands(rootCmd, subcommands)

	applyRootUsageFunc(rootCmd)
	applyCommandCompat1_8(rootCmd, &noMetrics, &noPortForwarding)

	return rootCmd
}
func PachctlCmd ( ) * cobra . Command { var verbose bool var noMetrics bool var noPortForwarding bool raw := false rawFlags := pflag . NewFlagSet ( " " , pflag . ContinueOnError ) rawFlags . BoolVar ( & raw , " " , false , " " ) marshaller := & jsonpb . Marshaler { Indent : " " } rootCmd := & cobra . Command { Use : os . Args [ 0 ] , Long : `Access the Pachyderm API.

Environment variables:
  PACHD_ADDRESS=<host>:<port>, the pachd server to connect to (e.g. 127.0.0.1:30650).
  PACH_CONFIG=<path>, the path where pachctl will attempt to load your pach config.
  JAEGER_ENDPOINT=<host>:<port>, the Jaeger server to connect to, if PACH_ENABLE_TRACING is set
  PACH_ENABLE_TRACING={true,false}, If true, and JAEGER_ENDPOINT is set, attach a
    Jaeger trace to all outgoing RPCs
` , PersistentPreRun : func ( cmd * cobra . Command , args [ ] string ) { log . SetFormatter ( new ( prefixed . TextFormatter ) ) if ! verbose { log . SetLevel ( log . ErrorLevel ) // Silence grpc logs grpclog . SetLoggerV2 ( grpclog . NewLoggerV2 ( ioutil . Discard , ioutil . Discard , ioutil . Discard ) ) } else { log . SetLevel ( log . DebugLevel ) // etcd overrides grpc's logs--there's no way to enable one without // enabling both. // Error and warning logs are discarded because they will be // redundantly sent to the info logger. See: // https://godoc.org/google.golang.org/grpc/grpclog#NewLoggerV2 logger := log . StandardLogger ( ) etcd . SetLogger ( grpclog . NewLoggerV2 ( logutil . NewGRPCLogWriter ( logger , " " ) , ioutil . Discard , ioutil . Discard , ) ) } } , BashCompletionFunction : bashCompletionFunc , } rootCmd . PersistentFlags ( ) . BoolVarP ( & verbose , " " , " " , false , " " ) rootCmd . PersistentFlags ( ) . BoolVarP ( & noMetrics , " " , " " , false , " " ) rootCmd . PersistentFlags ( ) . BoolVarP ( & noPortForwarding , " " , " " , false , " " ) var subcommands [ ] * cobra . Command var clientOnly bool var timeoutFlag string versionCmd := & cobra . Command { Short : " " , Long : " " , Run : cmdutil . RunFixedArgs ( 0 , func ( args [ ] string ) ( retErr error ) { if clientOnly { if raw { if err := marshaller . Marshal ( os . Stdout , version . Version ) ; err != nil { return err } } else { fmt . Println ( version . PrettyPrintVersion ( version . Version ) ) } return nil } if ! noMetrics { start := time . Now ( ) startMetricsWait := metrics . StartReportAndFlushUserAction ( " " , start ) defer startMetricsWait ( ) defer func ( ) { finishMetricsWait := metrics . FinishReportAndFlushUserAction ( " " , retErr , start ) finishMetricsWait ( ) } ( ) } // Print header + client version writer := ansiterm . NewTabWriter ( os . Stdout , 20 , 1 , 3 , ' ' , 0 ) if raw { if err := marshaller . Marshal ( os . Stdout , version . Version ) ; err != nil { return err } } else { printVersionHeader ( writer ) printVersion ( writer , " " , version . Version ) if err := writer . Flush ( ) ; err != nil { return err } } // Dial pachd & get server version var pachClient * client . APIClient var err error if timeoutFlag != " " { var timeout time . Duration timeout , err = time . ParseDuration ( timeoutFlag ) if err != nil { return fmt . Errorf ( " " , timeout , err ) } pachClient , err = client . NewOnUserMachine ( false , ! noPortForwarding , " " , client . WithDialTimeout ( timeout ) ) } else { pachClient , err = client . NewOnUserMachine ( false , ! noPortForwarding , " " ) } if err != nil { return err } defer pachClient . Close ( ) ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Second ) defer cancel ( ) version , err := pachClient . GetVersion ( ctx , & types . Empty { } ) if err != nil { buf := bytes . NewBufferString ( " " ) errWriter := ansiterm . NewTabWriter ( buf , 20 , 1 , 3 , ' ' , 0 ) fmt . Fprintf ( errWriter , " \t \n \n \n " , pachClient . GetAddress ( ) , grpc . ErrorDesc ( err ) ) errWriter . Flush ( ) return errors . New ( buf . String ( ) ) } // print server version if raw { if err := marshaller . Marshal ( os . Stdout , version ) ; err != nil { return err } } else { printVersion ( writer , " " , version ) if err := writer . Flush ( ) ; err != nil { return err } } return nil } ) , } versionCmd . Flags ( ) . BoolVar ( & clientOnly , " " , false , " " + " " + " " ) versionCmd . Flags ( ) . StringVar ( & timeoutFlag , " " , " " , " " + " " + " " + " " + " " ) versionCmd . Flags ( ) . AddFlagSet ( rawFlags ) subcommands = append ( subcommands , cmdutil . CreateAlias ( versionCmd , " " ) ) deleteAll := & cobra . Command { Short : " " , Long : `Delete all repos, commits, files, pipelines and jobs.
This resets the cluster to its initial state.` , Run : cmdutil . RunFixedArgs ( 0 , func ( args [ ] string ) error { client , err := client . NewOnUserMachine ( ! noMetrics , ! noPortForwarding , " " ) if err != nil { return err } defer client . Close ( ) red := color . New ( color . FgRed ) . SprintFunc ( ) var repos , pipelines [ ] string repoInfos , err := client . ListRepo ( ) if err != nil { return err } for _ , ri := range repoInfos { repos = append ( repos , red ( ri . Repo . Name ) ) } pipelineInfos , err := client . ListPipeline ( ) if err != nil { return err } for _ , pi := range pipelineInfos { pipelines = append ( pipelines , red ( pi . Pipeline . Name ) ) } fmt . Println ( " " ) if len ( repos ) > 0 { fmt . Printf ( " \n " , strings . Join ( repos , " " ) ) } if len ( pipelines ) > 0 { fmt . Printf ( " \n " , strings . Join ( pipelines , " " ) ) } fmt . Println ( " " ) r := bufio . NewReader ( os . Stdin ) bytes , err := r . ReadBytes ( '\n' ) if err != nil { return err } if bytes [ 0 ] == 'y' || bytes [ 0 ] == 'Y' { return client . DeleteAll ( ) } return nil } ) , } subcommands = append ( subcommands , cmdutil . CreateAlias ( deleteAll , " " ) ) var port uint16 var remotePort uint16 var samlPort uint16 var uiPort uint16 var uiWebsocketPort uint16 var pfsPort uint16 var s3gatewayPort uint16 var namespace string portForward := & cobra . Command { Short : " " , Long : " " , Run : cmdutil . RunFixedArgs ( 0 , func ( args [ ] string ) error { fw , err := client . NewPortForwarder ( namespace ) if err != nil { return err } if err = fw . Lock ( ) ; err != nil { return err } defer fw . Close ( ) failCount := 0 fmt . Println ( " " ) if err = fw . RunForDaemon ( port , remotePort ) ; err != nil { fmt . Printf ( " \n " , err ) failCount ++ } fmt . Println ( " " ) if err = fw . RunForSAMLACS ( samlPort ) ; err != nil { fmt . Printf ( " \n " , err ) failCount ++ } fmt . Printf ( " \n " , uiPort ) if err = fw . RunForDashUI ( uiPort ) ; err != nil { fmt . Printf ( " \n " , err ) failCount ++ } fmt . Println ( " " ) if err = fw . RunForDashWebSocket ( uiWebsocketPort ) ; err != nil { fmt . Printf ( " \n " , err ) failCount ++ } fmt . Println ( " " ) if err = fw . RunForPFS ( pfsPort ) ; err != nil { fmt . Printf ( " \n " , err ) failCount ++ } fmt . Println ( " " ) if err = fw . RunForS3Gateway ( s3gatewayPort ) ; err != nil { fmt . Printf ( " \n " , err ) failCount ++ } if failCount < 6 { fmt . Println ( " " ) ch := make ( chan os . Signal , 1 ) signal . Notify ( ch , os . Interrupt ) <- ch } return nil } ) , } portForward . Flags ( ) . Uint16VarP ( & port , " " , " " , 30650 , " " ) portForward . Flags ( ) . Uint16Var ( & remotePort , " " , 650 , " " ) portForward . Flags ( ) . Uint16Var ( & samlPort , " " , 30654 , " " ) portForward . Flags ( ) . Uint16VarP ( & uiPort , " " , " " , 30080 , " " ) portForward . Flags ( ) . Uint16VarP ( & uiWebsocketPort , " " , " " , 30081 , " " ) portForward . Flags ( ) . Uint16VarP ( & pfsPort , " " , " " , 30652 , " " ) portForward . Flags ( ) . Uint16VarP ( & s3gatewayPort , " " , " " , 30600 , " " ) portForward . Flags ( ) . StringVar ( & namespace , " " , " " , " " ) subcommands = append ( subcommands , cmdutil . CreateAlias ( portForward , " " ) ) var install bool var path string completion := & cobra . Command { Short : " " , Long : " " , Run : cmdutil . RunFixedArgs ( 0 , func ( args [ ] string ) ( retErr error ) { var dest io . Writer if install { f , err := os . Create ( path ) if err != nil { if os . IsPermission ( err ) { fmt . Fprintf ( os . Stderr , " \n " ) } return err } defer func ( ) { if err := f . Close ( ) ; err != nil && retErr == nil { retErr = err } fmt . Printf ( " \n " , path ) } ( ) dest = f } else { dest = os . Stdout } // Remove 'hidden' flag from all commands so we can get bash completions for them as well var unhide func ( * cobra . Command ) unhide = func ( cmd * cobra . Command ) { cmd . Hidden = false for _ , subcmd := range cmd . Commands ( ) { unhide ( subcmd ) } } unhide ( rootCmd ) return rootCmd . GenBashCompletion ( dest ) } ) , } completion . Flags ( ) . BoolVar ( & install , " " , false , " " ) completion . Flags ( ) . StringVar ( & path , " " , " " , " " ) subcommands = append ( subcommands , cmdutil . CreateAlias ( completion , " " ) ) // Logical commands for grouping commands by verb (no run functions) deleteDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( deleteDocs , " " ) ) createDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( createDocs , " " ) ) updateDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( updateDocs , " " ) ) inspectDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( inspectDocs , " " ) ) listDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( listDocs , " " ) ) startDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( startDocs , " " ) ) finishDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( finishDocs , " " ) ) flushDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( flushDocs , " " ) ) subscribeDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( subscribeDocs , " " ) ) putDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( putDocs , " " ) ) copyDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( copyDocs , " " ) ) getDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( getDocs , " " ) ) globDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( globDocs , " " ) ) diffDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( diffDocs , " " ) ) stopDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( stopDocs , " " ) ) restartDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( restartDocs , " " ) ) editDocs := & cobra . Command { Short : " " , Long : " " , } subcommands = append ( subcommands , cmdutil . CreateAlias ( editDocs , " " ) ) subcommands = append ( subcommands , pfscmds . Cmds ( & noMetrics , & noPortForwarding ) ... ) subcommands = append ( subcommands , ppscmds . Cmds ( & noMetrics , & noPortForwarding ) ... ) subcommands = append ( subcommands , deploycmds . Cmds ( & noMetrics , & noPortForwarding ) ... ) subcommands = append ( subcommands , authcmds . Cmds ( & noMetrics , & noPortForwarding ) ... ) subcommands = append ( subcommands , enterprisecmds . Cmds ( & noMetrics , & noPortForwarding ) ... ) subcommands = append ( subcommands , admincmds . Cmds ( & noMetrics , & noPortForwarding ) ... ) subcommands = append ( subcommands , debugcmds . Cmds ( & noMetrics , & noPortForwarding ) ... ) cmdutil . MergeCommands ( rootCmd , subcommands ) applyRootUsageFunc ( rootCmd ) applyCommandCompat1_8 ( rootCmd , & noMetrics , & noPortForwarding ) return rootCmd }
// PachctlCmd creates a cobra.Command which can deploy pachyderm clusters and
// interact with them (it implements the pachctl binary).
PachctlCmd creates a cobra . Command which can deploy pachyderm clusters and interact with them ( it implements the pachctl binary ) .