github.1git.de/docker/cli@v26.1.3+incompatible/cli-plugins/manager/cobra.go (about) 1 package manager 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "strings" 8 "sync" 9 10 "github.com/docker/cli/cli/command" 11 "github.com/spf13/cobra" 12 "go.opentelemetry.io/otel/attribute" 13 ) 14 15 const ( 16 // CommandAnnotationPlugin is added to every stub command added by 17 // AddPluginCommandStubs with the value "true" and so can be 18 // used to distinguish plugin stubs from regular commands. 19 CommandAnnotationPlugin = "com.docker.cli.plugin" 20 21 // CommandAnnotationPluginVendor is added to every stub command 22 // added by AddPluginCommandStubs and contains the vendor of 23 // that plugin. 24 CommandAnnotationPluginVendor = "com.docker.cli.plugin.vendor" 25 26 // CommandAnnotationPluginVersion is added to every stub command 27 // added by AddPluginCommandStubs and contains the version of 28 // that plugin. 29 CommandAnnotationPluginVersion = "com.docker.cli.plugin.version" 30 31 // CommandAnnotationPluginInvalid is added to any stub command 32 // added by AddPluginCommandStubs for an invalid command (that 33 // is, one which failed it's candidate test) and contains the 34 // reason for the failure. 35 CommandAnnotationPluginInvalid = "com.docker.cli.plugin-invalid" 36 37 // CommandAnnotationPluginCommandPath is added to overwrite the 38 // command path for a plugin invocation. 39 CommandAnnotationPluginCommandPath = "com.docker.cli.plugin.command_path" 40 ) 41 42 var pluginCommandStubsOnce sync.Once 43 44 // AddPluginCommandStubs adds a stub cobra.Commands for each valid and invalid 45 // plugin. The command stubs will have several annotations added, see 46 // `CommandAnnotationPlugin*`. 47 func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err error) { 48 pluginCommandStubsOnce.Do(func() { 49 var plugins []Plugin 50 plugins, err = ListPlugins(dockerCli, rootCmd) 51 if err != nil { 52 return 53 } 54 for _, p := range plugins { 55 p := p 56 vendor := p.Vendor 57 if vendor == "" { 58 vendor = "unknown" 59 } 60 annotations := map[string]string{ 61 CommandAnnotationPlugin: "true", 62 CommandAnnotationPluginVendor: vendor, 63 CommandAnnotationPluginVersion: p.Version, 64 } 65 if p.Err != nil { 66 annotations[CommandAnnotationPluginInvalid] = p.Err.Error() 67 } 68 rootCmd.AddCommand(&cobra.Command{ 69 Use: p.Name, 70 Short: p.ShortDescription, 71 Run: func(_ *cobra.Command, _ []string) {}, 72 Annotations: annotations, 73 DisableFlagParsing: true, 74 RunE: func(cmd *cobra.Command, args []string) error { 75 flags := rootCmd.PersistentFlags() 76 flags.SetOutput(nil) 77 perr := flags.Parse(args) 78 if perr != nil { 79 return err 80 } 81 if flags.Changed("help") { 82 cmd.HelpFunc()(rootCmd, args) 83 return nil 84 } 85 return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'", cmd.Name()) 86 }, 87 ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 88 // Delegate completion to plugin 89 cargs := []string{p.Path, cobra.ShellCompRequestCmd, p.Name} 90 cargs = append(cargs, args...) 91 cargs = append(cargs, toComplete) 92 os.Args = cargs 93 runCommand, runErr := PluginRunCommand(dockerCli, p.Name, cmd) 94 if runErr != nil { 95 return nil, cobra.ShellCompDirectiveError 96 } 97 runErr = runCommand.Run() 98 if runErr == nil { 99 os.Exit(0) // plugin already rendered complete data 100 } 101 return nil, cobra.ShellCompDirectiveError 102 }, 103 }) 104 } 105 }) 106 return err 107 } 108 109 const ( 110 dockerCliAttributePrefix = attribute.Key("docker.cli") 111 112 cobraCommandPath = attribute.Key("cobra.command_path") 113 ) 114 115 func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set { 116 commandPath := cmd.Annotations[CommandAnnotationPluginCommandPath] 117 if commandPath == "" { 118 commandPath = fmt.Sprintf("%s %s", cmd.CommandPath(), plugin.Name) 119 } 120 121 attrSet := attribute.NewSet( 122 cobraCommandPath.String(commandPath), 123 ) 124 125 kvs := make([]attribute.KeyValue, 0, attrSet.Len()) 126 for iter := attrSet.Iter(); iter.Next(); { 127 attr := iter.Attribute() 128 kvs = append(kvs, attribute.KeyValue{ 129 Key: dockerCliAttributePrefix + "." + attr.Key, 130 Value: attr.Value, 131 }) 132 } 133 return attribute.NewSet(kvs...) 134 } 135 136 func appendPluginResourceAttributesEnvvar(env []string, cmd *cobra.Command, plugin Plugin) []string { 137 if attrs := getPluginResourceAttributes(cmd, plugin); attrs.Len() > 0 { 138 // values in environment variables need to be in baggage format 139 // otel/baggage package can be used after update to v1.22, currently it encodes incorrectly 140 attrsSlice := make([]string, attrs.Len()) 141 for iter := attrs.Iter(); iter.Next(); { 142 i, v := iter.IndexedAttribute() 143 attrsSlice[i] = string(v.Key) + "=" + url.PathEscape(v.Value.AsString()) 144 } 145 env = append(env, ResourceAttributesEnvvar+"="+strings.Join(attrsSlice, ",")) 146 } 147 return env 148 }