github.com/moby/docker@v26.1.3+incompatible/pkg/plugins/discovery.go (about) 1 package plugins // import "github.com/docker/docker/pkg/plugins" 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/fs" 7 "net/url" 8 "os" 9 "path/filepath" 10 "strings" 11 "sync" 12 13 "github.com/containerd/containerd/pkg/userns" 14 "github.com/containerd/log" 15 "github.com/pkg/errors" 16 ) 17 18 // ErrNotFound plugin not found 19 var ErrNotFound = errors.New("plugin not found") 20 21 const defaultSocketsPath = "/run/docker/plugins" 22 23 // LocalRegistry defines a registry that is local (using unix socket). 24 type LocalRegistry struct { 25 socketsPath string 26 specsPaths []string 27 } 28 29 func NewLocalRegistry() LocalRegistry { 30 return LocalRegistry{ 31 socketsPath: defaultSocketsPath, 32 specsPaths: specsPaths(), 33 } 34 } 35 36 // Scan scans all the plugin paths and returns all the names it found 37 func (l *LocalRegistry) Scan() ([]string, error) { 38 var names []string 39 dirEntries, err := os.ReadDir(l.socketsPath) 40 if err != nil && !os.IsNotExist(err) { 41 return nil, errors.Wrap(err, "error reading dir entries") 42 } 43 44 for _, entry := range dirEntries { 45 if entry.IsDir() { 46 fi, err := os.Stat(filepath.Join(l.socketsPath, entry.Name(), entry.Name()+".sock")) 47 if err != nil { 48 continue 49 } 50 51 entry = fs.FileInfoToDirEntry(fi) 52 } 53 54 if entry.Type()&os.ModeSocket != 0 { 55 names = append(names, strings.TrimSuffix(filepath.Base(entry.Name()), filepath.Ext(entry.Name()))) 56 } 57 } 58 59 for _, p := range l.specsPaths { 60 dirEntries, err = os.ReadDir(p) 61 if err != nil { 62 if os.IsNotExist(err) { 63 continue 64 } 65 if os.IsPermission(err) && userns.RunningInUserNS() { 66 log.L.Debug(err.Error()) 67 continue 68 } 69 return nil, errors.Wrap(err, "error reading dir entries") 70 } 71 for _, entry := range dirEntries { 72 if entry.IsDir() { 73 infos, err := os.ReadDir(filepath.Join(p, entry.Name())) 74 if err != nil { 75 continue 76 } 77 78 for _, info := range infos { 79 if strings.TrimSuffix(info.Name(), filepath.Ext(info.Name())) == entry.Name() { 80 entry = info 81 break 82 } 83 } 84 } 85 86 switch ext := filepath.Ext(entry.Name()); ext { 87 case ".spec", ".json": 88 plugin := strings.TrimSuffix(entry.Name(), ext) 89 names = append(names, plugin) 90 default: 91 } 92 } 93 } 94 return names, nil 95 } 96 97 // Plugin returns the plugin registered with the given name (or returns an error). 98 func (l *LocalRegistry) Plugin(name string) (*Plugin, error) { 99 socketPaths := pluginPaths(l.socketsPath, name, ".sock") 100 for _, p := range socketPaths { 101 if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 { 102 return NewLocalPlugin(name, "unix://"+p), nil 103 } 104 } 105 106 var txtSpecPaths []string 107 for _, p := range l.specsPaths { 108 txtSpecPaths = append(txtSpecPaths, pluginPaths(p, name, ".spec")...) 109 txtSpecPaths = append(txtSpecPaths, pluginPaths(p, name, ".json")...) 110 } 111 112 for _, p := range txtSpecPaths { 113 if _, err := os.Stat(p); err == nil { 114 if strings.HasSuffix(p, ".json") { 115 return readPluginJSONInfo(name, p) 116 } 117 return readPluginInfo(name, p) 118 } 119 } 120 return nil, errors.Wrapf(ErrNotFound, "could not find plugin %s in v1 plugin registry", name) 121 } 122 123 // SpecsPaths returns paths in which to look for plugins, in order of priority. 124 // 125 // On Windows: 126 // 127 // - "%programdata%\docker\plugins" 128 // 129 // On Unix in non-rootless mode: 130 // 131 // - "/etc/docker/plugins" 132 // - "/usr/lib/docker/plugins" 133 // 134 // On Unix in rootless-mode: 135 // 136 // - "$XDG_CONFIG_HOME/docker/plugins" (or "/etc/docker/plugins" if $XDG_CONFIG_HOME is not set) 137 // - "$HOME/.local/lib/docker/plugins" (pr "/usr/lib/docker/plugins" if $HOME is set) 138 func SpecsPaths() []string { 139 return specsPaths() 140 } 141 142 func readPluginInfo(name, path string) (*Plugin, error) { 143 content, err := os.ReadFile(path) 144 if err != nil { 145 return nil, err 146 } 147 addr := strings.TrimSpace(string(content)) 148 149 u, err := url.Parse(addr) 150 if err != nil { 151 return nil, err 152 } 153 154 if len(u.Scheme) == 0 { 155 return nil, fmt.Errorf("Unknown protocol") 156 } 157 158 return NewLocalPlugin(name, addr), nil 159 } 160 161 func readPluginJSONInfo(name, path string) (*Plugin, error) { 162 f, err := os.Open(path) 163 if err != nil { 164 return nil, err 165 } 166 defer f.Close() 167 168 var p Plugin 169 if err := json.NewDecoder(f).Decode(&p); err != nil { 170 return nil, err 171 } 172 p.name = name 173 if p.TLSConfig != nil && len(p.TLSConfig.CAFile) == 0 { 174 p.TLSConfig.InsecureSkipVerify = true 175 } 176 p.activateWait = sync.NewCond(&sync.Mutex{}) 177 178 return &p, nil 179 } 180 181 func pluginPaths(base, name, ext string) []string { 182 return []string{ 183 filepath.Join(base, name+ext), 184 filepath.Join(base, name, name+ext), 185 } 186 }