github.com/hms58/moby@v1.13.1/plugin/manager.go (about) 1 package plugin 2 3 import ( 4 "encoding/json" 5 "io" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "reflect" 10 "regexp" 11 "strings" 12 "sync" 13 14 "github.com/Sirupsen/logrus" 15 "github.com/docker/distribution/digest" 16 "github.com/docker/docker/api/types" 17 "github.com/docker/docker/image" 18 "github.com/docker/docker/layer" 19 "github.com/docker/docker/libcontainerd" 20 "github.com/docker/docker/pkg/ioutils" 21 "github.com/docker/docker/pkg/mount" 22 "github.com/docker/docker/plugin/v2" 23 "github.com/docker/docker/reference" 24 "github.com/docker/docker/registry" 25 "github.com/pkg/errors" 26 ) 27 28 const configFileName = "config.json" 29 const rootFSFileName = "rootfs" 30 31 var validFullID = regexp.MustCompile(`^([a-f0-9]{64})$`) 32 33 func (pm *Manager) restorePlugin(p *v2.Plugin) error { 34 if p.IsEnabled() { 35 return pm.restore(p) 36 } 37 return nil 38 } 39 40 type eventLogger func(id, name, action string) 41 42 // ManagerConfig defines configuration needed to start new manager. 43 type ManagerConfig struct { 44 Store *Store // remove 45 Executor libcontainerd.Remote 46 RegistryService registry.Service 47 LiveRestoreEnabled bool // TODO: remove 48 LogPluginEvent eventLogger 49 Root string 50 ExecRoot string 51 } 52 53 // Manager controls the plugin subsystem. 54 type Manager struct { 55 config ManagerConfig 56 mu sync.RWMutex // protects cMap 57 muGC sync.RWMutex // protects blobstore deletions 58 cMap map[*v2.Plugin]*controller 59 containerdClient libcontainerd.Client 60 blobStore *basicBlobStore 61 } 62 63 // controller represents the manager's control on a plugin. 64 type controller struct { 65 restart bool 66 exitChan chan bool 67 timeoutInSecs int 68 } 69 70 // pluginRegistryService ensures that all resolved repositories 71 // are of the plugin class. 72 type pluginRegistryService struct { 73 registry.Service 74 } 75 76 func (s pluginRegistryService) ResolveRepository(name reference.Named) (repoInfo *registry.RepositoryInfo, err error) { 77 repoInfo, err = s.Service.ResolveRepository(name) 78 if repoInfo != nil { 79 repoInfo.Class = "plugin" 80 } 81 return 82 } 83 84 // NewManager returns a new plugin manager. 85 func NewManager(config ManagerConfig) (*Manager, error) { 86 if config.RegistryService != nil { 87 config.RegistryService = pluginRegistryService{config.RegistryService} 88 } 89 manager := &Manager{ 90 config: config, 91 } 92 if err := os.MkdirAll(manager.config.Root, 0700); err != nil { 93 return nil, errors.Wrapf(err, "failed to mkdir %v", manager.config.Root) 94 } 95 if err := os.MkdirAll(manager.config.ExecRoot, 0700); err != nil { 96 return nil, errors.Wrapf(err, "failed to mkdir %v", manager.config.ExecRoot) 97 } 98 if err := os.MkdirAll(manager.tmpDir(), 0700); err != nil { 99 return nil, errors.Wrapf(err, "failed to mkdir %v", manager.tmpDir()) 100 } 101 var err error 102 manager.containerdClient, err = config.Executor.Client(manager) // todo: move to another struct 103 if err != nil { 104 return nil, errors.Wrap(err, "failed to create containerd client") 105 } 106 manager.blobStore, err = newBasicBlobStore(filepath.Join(manager.config.Root, "storage/blobs")) 107 if err != nil { 108 return nil, err 109 } 110 111 manager.cMap = make(map[*v2.Plugin]*controller) 112 if err := manager.reload(); err != nil { 113 return nil, errors.Wrap(err, "failed to restore plugins") 114 } 115 return manager, nil 116 } 117 118 func (pm *Manager) tmpDir() string { 119 return filepath.Join(pm.config.Root, "tmp") 120 } 121 122 // StateChanged updates plugin internals using libcontainerd events. 123 func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error { 124 logrus.Debugf("plugin state changed %s %#v", id, e) 125 126 switch e.State { 127 case libcontainerd.StateExit: 128 p, err := pm.config.Store.GetV2Plugin(id) 129 if err != nil { 130 return err 131 } 132 133 pm.mu.RLock() 134 c := pm.cMap[p] 135 136 if c.exitChan != nil { 137 close(c.exitChan) 138 } 139 restart := c.restart 140 pm.mu.RUnlock() 141 142 os.RemoveAll(filepath.Join(pm.config.ExecRoot, id)) 143 144 if p.PropagatedMount != "" { 145 if err := mount.Unmount(p.PropagatedMount); err != nil { 146 logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err) 147 } 148 propRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount") 149 if err := mount.Unmount(propRoot); err != nil { 150 logrus.Warn("Could not unmount %s: %v", propRoot, err) 151 } 152 } 153 154 if restart { 155 pm.enable(p, c, true) 156 } 157 } 158 159 return nil 160 } 161 162 func (pm *Manager) reload() error { // todo: restore 163 dir, err := ioutil.ReadDir(pm.config.Root) 164 if err != nil { 165 return errors.Wrapf(err, "failed to read %v", pm.config.Root) 166 } 167 plugins := make(map[string]*v2.Plugin) 168 for _, v := range dir { 169 if validFullID.MatchString(v.Name()) { 170 p, err := pm.loadPlugin(v.Name()) 171 if err != nil { 172 return err 173 } 174 plugins[p.GetID()] = p 175 } 176 } 177 178 pm.config.Store.SetAll(plugins) 179 180 var wg sync.WaitGroup 181 wg.Add(len(plugins)) 182 for _, p := range plugins { 183 c := &controller{} // todo: remove this 184 pm.cMap[p] = c 185 go func(p *v2.Plugin) { 186 defer wg.Done() 187 if err := pm.restorePlugin(p); err != nil { 188 logrus.Errorf("failed to restore plugin '%s': %s", p.Name(), err) 189 return 190 } 191 192 if p.Rootfs != "" { 193 p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs") 194 } 195 196 // We should only enable rootfs propagation for certain plugin types that need it. 197 for _, typ := range p.PluginObj.Config.Interface.Types { 198 if (typ.Capability == "volumedriver" || typ.Capability == "graphdriver") && typ.Prefix == "docker" && strings.HasPrefix(typ.Version, "1.") { 199 if p.PluginObj.Config.PropagatedMount != "" { 200 propRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount") 201 202 // check if we need to migrate an older propagated mount from before 203 // these mounts were stored outside the plugin rootfs 204 if _, err := os.Stat(propRoot); os.IsNotExist(err) { 205 if _, err := os.Stat(p.PropagatedMount); err == nil { 206 // make sure nothing is mounted here 207 // don't care about errors 208 mount.Unmount(p.PropagatedMount) 209 if err := os.Rename(p.PropagatedMount, propRoot); err != nil { 210 logrus.WithError(err).WithField("dir", propRoot).Error("error migrating propagated mount storage") 211 } 212 if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil { 213 logrus.WithError(err).WithField("dir", p.PropagatedMount).Error("error migrating propagated mount storage") 214 } 215 } 216 } 217 218 if err := os.MkdirAll(propRoot, 0755); err != nil { 219 logrus.Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err) 220 } 221 // TODO: sanitize PropagatedMount and prevent breakout 222 p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount) 223 if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil { 224 logrus.Errorf("failed to create PropagatedMount directory at %s: %v", p.PropagatedMount, err) 225 return 226 } 227 } 228 } 229 } 230 231 pm.save(p) 232 requiresManualRestore := !pm.config.LiveRestoreEnabled && p.IsEnabled() 233 234 if requiresManualRestore { 235 // if liveRestore is not enabled, the plugin will be stopped now so we should enable it 236 if err := pm.enable(p, c, true); err != nil { 237 logrus.Errorf("failed to enable plugin '%s': %s", p.Name(), err) 238 } 239 } 240 }(p) 241 } 242 wg.Wait() 243 return nil 244 } 245 246 func (pm *Manager) loadPlugin(id string) (*v2.Plugin, error) { 247 p := filepath.Join(pm.config.Root, id, configFileName) 248 dt, err := ioutil.ReadFile(p) 249 if err != nil { 250 return nil, errors.Wrapf(err, "error reading %v", p) 251 } 252 var plugin v2.Plugin 253 if err := json.Unmarshal(dt, &plugin); err != nil { 254 return nil, errors.Wrapf(err, "error decoding %v", p) 255 } 256 return &plugin, nil 257 } 258 259 func (pm *Manager) save(p *v2.Plugin) error { 260 pluginJSON, err := json.Marshal(p) 261 if err != nil { 262 return errors.Wrap(err, "failed to marshal plugin json") 263 } 264 if err := ioutils.AtomicWriteFile(filepath.Join(pm.config.Root, p.GetID(), configFileName), pluginJSON, 0600); err != nil { 265 return errors.Wrap(err, "failed to write atomically plugin json") 266 } 267 return nil 268 } 269 270 // GC cleans up unrefrenced blobs. This is recommended to run in a goroutine 271 func (pm *Manager) GC() { 272 pm.muGC.Lock() 273 defer pm.muGC.Unlock() 274 275 whitelist := make(map[digest.Digest]struct{}) 276 for _, p := range pm.config.Store.GetAll() { 277 whitelist[p.Config] = struct{}{} 278 for _, b := range p.Blobsums { 279 whitelist[b] = struct{}{} 280 } 281 } 282 283 pm.blobStore.gc(whitelist) 284 } 285 286 type logHook struct{ id string } 287 288 func (logHook) Levels() []logrus.Level { 289 return logrus.AllLevels 290 } 291 292 func (l logHook) Fire(entry *logrus.Entry) error { 293 entry.Data = logrus.Fields{"plugin": l.id} 294 return nil 295 } 296 297 func attachToLog(id string) func(libcontainerd.IOPipe) error { 298 return func(iop libcontainerd.IOPipe) error { 299 iop.Stdin.Close() 300 301 logger := logrus.New() 302 logger.Hooks.Add(logHook{id}) 303 // TODO: cache writer per id 304 w := logger.Writer() 305 go func() { 306 io.Copy(w, iop.Stdout) 307 }() 308 go func() { 309 // TODO: update logrus and use logger.WriterLevel 310 io.Copy(w, iop.Stderr) 311 }() 312 return nil 313 } 314 } 315 316 func validatePrivileges(requiredPrivileges, privileges types.PluginPrivileges) error { 317 // todo: make a better function that doesn't check order 318 if !reflect.DeepEqual(privileges, requiredPrivileges) { 319 return errors.New("incorrect privileges") 320 } 321 return nil 322 } 323 324 func configToRootFS(c []byte) (*image.RootFS, error) { 325 var pluginConfig types.PluginConfig 326 if err := json.Unmarshal(c, &pluginConfig); err != nil { 327 return nil, err 328 } 329 // validation for empty rootfs is in distribution code 330 if pluginConfig.Rootfs == nil { 331 return nil, nil 332 } 333 334 return rootFSFromPlugin(pluginConfig.Rootfs), nil 335 } 336 337 func rootFSFromPlugin(pluginfs *types.PluginConfigRootfs) *image.RootFS { 338 rootFS := image.RootFS{ 339 Type: pluginfs.Type, 340 DiffIDs: make([]layer.DiffID, len(pluginfs.DiffIds)), 341 } 342 for i := range pluginfs.DiffIds { 343 rootFS.DiffIDs[i] = layer.DiffID(pluginfs.DiffIds[i]) 344 } 345 346 return &rootFS 347 }