github.com/kaisenlinux/docker@v0.0.0-20230510090727-ea55db55fac7/cli/cli-plugins/manager/plugin.go (about) 1 package manager 2 3 import ( 4 "encoding/json" 5 "path/filepath" 6 "regexp" 7 "strings" 8 9 "github.com/pkg/errors" 10 "github.com/spf13/cobra" 11 ) 12 13 var ( 14 pluginNameRe = regexp.MustCompile("^[a-z][a-z0-9]*$") 15 ) 16 17 // Plugin represents a potential plugin with all it's metadata. 18 type Plugin struct { 19 Metadata 20 21 Name string `json:",omitempty"` 22 Path string `json:",omitempty"` 23 24 // Err is non-nil if the plugin failed one of the candidate tests. 25 Err error `json:",omitempty"` 26 27 // ShadowedPaths contains the paths of any other plugins which this plugin takes precedence over. 28 ShadowedPaths []string `json:",omitempty"` 29 } 30 31 // newPlugin determines if the given candidate is valid and returns a 32 // Plugin. If the candidate fails one of the tests then `Plugin.Err` 33 // is set, and is always a `pluginError`, but the `Plugin` is still 34 // returned with no error. An error is only returned due to a 35 // non-recoverable error. 36 func newPlugin(c Candidate, rootcmd *cobra.Command) (Plugin, error) { 37 path := c.Path() 38 if path == "" { 39 return Plugin{}, errors.New("plugin candidate path cannot be empty") 40 } 41 42 // The candidate listing process should have skipped anything 43 // which would fail here, so there are all real errors. 44 fullname := filepath.Base(path) 45 if fullname == "." { 46 return Plugin{}, errors.Errorf("unable to determine basename of plugin candidate %q", path) 47 } 48 var err error 49 if fullname, err = trimExeSuffix(fullname); err != nil { 50 return Plugin{}, errors.Wrapf(err, "plugin candidate %q", path) 51 } 52 if !strings.HasPrefix(fullname, NamePrefix) { 53 return Plugin{}, errors.Errorf("plugin candidate %q: does not have %q prefix", path, NamePrefix) 54 } 55 56 p := Plugin{ 57 Name: strings.TrimPrefix(fullname, NamePrefix), 58 Path: path, 59 } 60 61 // Now apply the candidate tests, so these update p.Err. 62 if !pluginNameRe.MatchString(p.Name) { 63 p.Err = NewPluginError("plugin candidate %q did not match %q", p.Name, pluginNameRe.String()) 64 return p, nil 65 } 66 67 if rootcmd != nil { 68 for _, cmd := range rootcmd.Commands() { 69 // Ignore conflicts with commands which are 70 // just plugin stubs (i.e. from a previous 71 // call to AddPluginCommandStubs). 72 if p := cmd.Annotations[CommandAnnotationPlugin]; p == "true" { 73 continue 74 } 75 if cmd.Name() == p.Name { 76 p.Err = NewPluginError("plugin %q duplicates builtin command", p.Name) 77 return p, nil 78 } 79 if cmd.HasAlias(p.Name) { 80 p.Err = NewPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name()) 81 return p, nil 82 } 83 } 84 } 85 86 // We are supposed to check for relevant execute permissions here. Instead we rely on an attempt to execute. 87 meta, err := c.Metadata() 88 if err != nil { 89 p.Err = wrapAsPluginError(err, "failed to fetch metadata") 90 return p, nil 91 } 92 93 if err := json.Unmarshal(meta, &p.Metadata); err != nil { 94 p.Err = wrapAsPluginError(err, "invalid metadata") 95 return p, nil 96 } 97 if p.Metadata.SchemaVersion != "0.1.0" { 98 p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion) 99 return p, nil 100 } 101 if p.Metadata.Vendor == "" { 102 p.Err = NewPluginError("plugin metadata does not define a vendor") 103 return p, nil 104 } 105 return p, nil 106 }