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  }