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  }