github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/cli-plugins/plugin/plugin.go (about) 1 package plugin 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "sync" 8 9 "github.com/docker/cli/cli" 10 "github.com/docker/cli/cli-plugins/manager" 11 "github.com/docker/cli/cli/command" 12 "github.com/docker/cli/cli/connhelper" 13 "github.com/docker/docker/client" 14 "github.com/spf13/cobra" 15 ) 16 17 // PersistentPreRunE must be called by any plugin command (or 18 // subcommand) which uses the cobra `PersistentPreRun*` hook. Plugins 19 // which do not make use of `PersistentPreRun*` do not need to call 20 // this (although it remains safe to do so). Plugins are recommended 21 // to use `PersistenPreRunE` to enable the error to be 22 // returned. Should not be called outside of a command's 23 // PersistentPreRunE hook and must not be run unless Run has been 24 // called. 25 var PersistentPreRunE func(*cobra.Command, []string) error 26 27 func runPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager.Metadata) error { 28 tcmd := newPluginCommand(dockerCli, plugin, meta) 29 30 var persistentPreRunOnce sync.Once 31 PersistentPreRunE = func(_ *cobra.Command, _ []string) error { 32 var err error 33 persistentPreRunOnce.Do(func() { 34 var opts []command.InitializeOpt 35 if os.Getenv("DOCKER_CLI_PLUGIN_USE_DIAL_STDIO") != "" { 36 opts = append(opts, withPluginClientConn(plugin.Name())) 37 } 38 err = tcmd.Initialize(opts...) 39 }) 40 return err 41 } 42 43 cmd, args, err := tcmd.HandleGlobalFlags() 44 if err != nil { 45 return err 46 } 47 // We've parsed global args already, so reset args to those 48 // which remain. 49 cmd.SetArgs(args) 50 return cmd.Execute() 51 } 52 53 // Run is the top-level entry point to the CLI plugin framework. It should be called from your plugin's `main()` function. 54 func Run(makeCmd func(command.Cli) *cobra.Command, meta manager.Metadata) { 55 dockerCli, err := command.NewDockerCli() 56 if err != nil { 57 fmt.Fprintln(os.Stderr, err) 58 os.Exit(1) 59 } 60 61 plugin := makeCmd(dockerCli) 62 63 if err := runPlugin(dockerCli, plugin, meta); err != nil { 64 if sterr, ok := err.(cli.StatusError); ok { 65 if sterr.Status != "" { 66 fmt.Fprintln(dockerCli.Err(), sterr.Status) 67 } 68 // StatusError should only be used for errors, and all errors should 69 // have a non-zero exit status, so never exit with 0 70 if sterr.StatusCode == 0 { 71 os.Exit(1) 72 } 73 os.Exit(sterr.StatusCode) 74 } 75 fmt.Fprintln(dockerCli.Err(), err) 76 os.Exit(1) 77 } 78 } 79 80 func withPluginClientConn(name string) command.InitializeOpt { 81 return command.WithInitializeClient(func(dockerCli *command.DockerCli) (client.APIClient, error) { 82 cmd := "docker" 83 if x := os.Getenv(manager.ReexecEnvvar); x != "" { 84 cmd = x 85 } 86 var flags []string 87 88 // Accumulate all the global arguments, that is those 89 // up to (but not including) the plugin's name. This 90 // ensures that `docker system dial-stdio` is 91 // evaluating the same set of `--config`, `--tls*` etc 92 // global options as the plugin was called with, which 93 // in turn is the same as what the original docker 94 // invocation was passed. 95 for _, a := range os.Args[1:] { 96 if a == name { 97 break 98 } 99 flags = append(flags, a) 100 } 101 flags = append(flags, "system", "dial-stdio") 102 103 helper, err := connhelper.GetCommandConnectionHelper(cmd, flags...) 104 if err != nil { 105 return nil, err 106 } 107 108 return client.NewClientWithOpts(client.WithDialContext(helper.Dialer)) 109 }) 110 } 111 112 func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager.Metadata) *cli.TopLevelCommand { 113 name := plugin.Name() 114 fullname := manager.NamePrefix + name 115 116 cmd := &cobra.Command{ 117 Use: fmt.Sprintf("docker [OPTIONS] %s [ARG...]", name), 118 Short: fullname + " is a Docker CLI plugin", 119 SilenceUsage: true, 120 SilenceErrors: true, 121 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 122 // We can't use this as the hook directly since it is initialised later (in runPlugin) 123 return PersistentPreRunE(cmd, args) 124 }, 125 TraverseChildren: true, 126 DisableFlagsInUseLine: true, 127 } 128 opts, flags := cli.SetupPluginRootCommand(cmd) 129 130 cmd.SetOutput(dockerCli.Out()) 131 132 cmd.AddCommand( 133 plugin, 134 newMetadataSubcommand(plugin, meta), 135 ) 136 137 cli.DisableFlagsInUseLine(cmd) 138 139 return cli.NewTopLevelCommand(cmd, dockerCli, opts, flags) 140 } 141 142 func newMetadataSubcommand(plugin *cobra.Command, meta manager.Metadata) *cobra.Command { 143 if meta.ShortDescription == "" { 144 meta.ShortDescription = plugin.Short 145 } 146 cmd := &cobra.Command{ 147 Use: manager.MetadataSubcommandName, 148 Hidden: true, 149 // Suppress the global/parent PersistentPreRunE, which 150 // needlessly initializes the client and tries to 151 // connect to the daemon. 152 PersistentPreRun: func(cmd *cobra.Command, args []string) {}, 153 RunE: func(cmd *cobra.Command, args []string) error { 154 enc := json.NewEncoder(os.Stdout) 155 enc.SetEscapeHTML(false) 156 enc.SetIndent("", " ") 157 return enc.Encode(meta) 158 }, 159 } 160 return cmd 161 }