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