github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/plugin/backend_linux.go (about) 1 // +build linux 2 3 package plugin 4 5 import ( 6 "archive/tar" 7 "compress/gzip" 8 "encoding/json" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "os" 14 "path" 15 "path/filepath" 16 "strings" 17 18 "github.com/Sirupsen/logrus" 19 "github.com/docker/distribution/manifest/schema2" 20 "github.com/docker/docker/api/types" 21 "github.com/docker/docker/distribution" 22 progressutils "github.com/docker/docker/distribution/utils" 23 "github.com/docker/docker/distribution/xfer" 24 "github.com/docker/docker/image" 25 "github.com/docker/docker/layer" 26 "github.com/docker/docker/pkg/chrootarchive" 27 "github.com/docker/docker/pkg/pools" 28 "github.com/docker/docker/pkg/progress" 29 "github.com/docker/docker/plugin/v2" 30 "github.com/docker/docker/reference" 31 "github.com/opencontainers/go-digest" 32 "github.com/pkg/errors" 33 "golang.org/x/net/context" 34 ) 35 36 // Disable deactivates a plugin. This means resources (volumes, networks) cant use them. 37 func (pm *Manager) Disable(refOrID string, config *types.PluginDisableConfig) error { 38 p, err := pm.config.Store.GetV2Plugin(refOrID) 39 if err != nil { 40 return err 41 } 42 pm.mu.RLock() 43 c := pm.cMap[p] 44 pm.mu.RUnlock() 45 46 if !config.ForceDisable && p.GetRefCount() > 0 { 47 return fmt.Errorf("plugin %s is in use", p.Name()) 48 } 49 50 if err := pm.disable(p, c); err != nil { 51 return err 52 } 53 pm.config.LogPluginEvent(p.GetID(), refOrID, "disable") 54 return nil 55 } 56 57 // Enable activates a plugin, which implies that they are ready to be used by containers. 58 func (pm *Manager) Enable(refOrID string, config *types.PluginEnableConfig) error { 59 p, err := pm.config.Store.GetV2Plugin(refOrID) 60 if err != nil { 61 return err 62 } 63 64 c := &controller{timeoutInSecs: config.Timeout} 65 if err := pm.enable(p, c, false); err != nil { 66 return err 67 } 68 pm.config.LogPluginEvent(p.GetID(), refOrID, "enable") 69 return nil 70 } 71 72 // Inspect examines a plugin config 73 func (pm *Manager) Inspect(refOrID string) (tp *types.Plugin, err error) { 74 p, err := pm.config.Store.GetV2Plugin(refOrID) 75 if err != nil { 76 return nil, err 77 } 78 79 return &p.PluginObj, nil 80 } 81 82 func (pm *Manager) pull(ctx context.Context, ref reference.Named, config *distribution.ImagePullConfig, outStream io.Writer) error { 83 if outStream != nil { 84 // Include a buffer so that slow client connections don't affect 85 // transfer performance. 86 progressChan := make(chan progress.Progress, 100) 87 88 writesDone := make(chan struct{}) 89 90 defer func() { 91 close(progressChan) 92 <-writesDone 93 }() 94 95 var cancelFunc context.CancelFunc 96 ctx, cancelFunc = context.WithCancel(ctx) 97 98 go func() { 99 progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan) 100 close(writesDone) 101 }() 102 103 config.ProgressOutput = progress.ChanOutput(progressChan) 104 } else { 105 config.ProgressOutput = progress.DiscardOutput() 106 } 107 return distribution.Pull(ctx, ref, config) 108 } 109 110 type tempConfigStore struct { 111 config []byte 112 configDigest digest.Digest 113 } 114 115 func (s *tempConfigStore) Put(c []byte) (digest.Digest, error) { 116 dgst := digest.FromBytes(c) 117 118 s.config = c 119 s.configDigest = dgst 120 121 return dgst, nil 122 } 123 124 func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) { 125 if d != s.configDigest { 126 return nil, fmt.Errorf("digest not found") 127 } 128 return s.config, nil 129 } 130 131 func (s *tempConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) { 132 return configToRootFS(c) 133 } 134 135 func computePrivileges(c types.PluginConfig) (types.PluginPrivileges, error) { 136 var privileges types.PluginPrivileges 137 if c.Network.Type != "null" && c.Network.Type != "bridge" && c.Network.Type != "" { 138 privileges = append(privileges, types.PluginPrivilege{ 139 Name: "network", 140 Description: "permissions to access a network", 141 Value: []string{c.Network.Type}, 142 }) 143 } 144 for _, mount := range c.Mounts { 145 if mount.Source != nil { 146 privileges = append(privileges, types.PluginPrivilege{ 147 Name: "mount", 148 Description: "host path to mount", 149 Value: []string{*mount.Source}, 150 }) 151 } 152 } 153 for _, device := range c.Linux.Devices { 154 if device.Path != nil { 155 privileges = append(privileges, types.PluginPrivilege{ 156 Name: "device", 157 Description: "host device to access", 158 Value: []string{*device.Path}, 159 }) 160 } 161 } 162 if c.Linux.AllowAllDevices { 163 privileges = append(privileges, types.PluginPrivilege{ 164 Name: "allow-all-devices", 165 Description: "allow 'rwm' access to all devices", 166 Value: []string{"true"}, 167 }) 168 } 169 if len(c.Linux.Capabilities) > 0 { 170 privileges = append(privileges, types.PluginPrivilege{ 171 Name: "capabilities", 172 Description: "list of additional capabilities required", 173 Value: c.Linux.Capabilities, 174 }) 175 } 176 177 return privileges, nil 178 } 179 180 // Privileges pulls a plugin config and computes the privileges required to install it. 181 func (pm *Manager) Privileges(ctx context.Context, ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) { 182 // create image store instance 183 cs := &tempConfigStore{} 184 185 // DownloadManager not defined because only pulling configuration. 186 pluginPullConfig := &distribution.ImagePullConfig{ 187 Config: distribution.Config{ 188 MetaHeaders: metaHeader, 189 AuthConfig: authConfig, 190 RegistryService: pm.config.RegistryService, 191 ImageEventLogger: func(string, string, string) {}, 192 ImageStore: cs, 193 }, 194 Schema2Types: distribution.PluginTypes, 195 } 196 197 if err := pm.pull(ctx, ref, pluginPullConfig, nil); err != nil { 198 return nil, err 199 } 200 201 if cs.config == nil { 202 return nil, errors.New("no configuration pulled") 203 } 204 var config types.PluginConfig 205 if err := json.Unmarshal(cs.config, &config); err != nil { 206 return nil, err 207 } 208 209 return computePrivileges(config) 210 } 211 212 // Pull pulls a plugin, check if the correct privileges are provided and install the plugin. 213 func (pm *Manager) Pull(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer) (err error) { 214 pm.muGC.RLock() 215 defer pm.muGC.RUnlock() 216 217 // revalidate because Pull is public 218 nameref, err := reference.ParseNamed(name) 219 if err != nil { 220 return errors.Wrapf(err, "failed to parse %q", name) 221 } 222 name = reference.WithDefaultTag(nameref).String() 223 224 if err := pm.config.Store.validateName(name); err != nil { 225 return err 226 } 227 228 tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs") 229 defer os.RemoveAll(tmpRootFSDir) 230 231 dm := &downloadManager{ 232 tmpDir: tmpRootFSDir, 233 blobStore: pm.blobStore, 234 } 235 236 pluginPullConfig := &distribution.ImagePullConfig{ 237 Config: distribution.Config{ 238 MetaHeaders: metaHeader, 239 AuthConfig: authConfig, 240 RegistryService: pm.config.RegistryService, 241 ImageEventLogger: pm.config.LogPluginEvent, 242 ImageStore: dm, 243 }, 244 DownloadManager: dm, // todo: reevaluate if possible to substitute distribution/xfer dependencies instead 245 Schema2Types: distribution.PluginTypes, 246 } 247 248 err = pm.pull(ctx, ref, pluginPullConfig, outStream) 249 if err != nil { 250 go pm.GC() 251 return err 252 } 253 254 if _, err := pm.createPlugin(name, dm.configDigest, dm.blobs, tmpRootFSDir, &privileges); err != nil { 255 return err 256 } 257 258 return nil 259 } 260 261 // List displays the list of plugins and associated metadata. 262 func (pm *Manager) List() ([]types.Plugin, error) { 263 plugins := pm.config.Store.GetAll() 264 out := make([]types.Plugin, 0, len(plugins)) 265 for _, p := range plugins { 266 out = append(out, p.PluginObj) 267 } 268 return out, nil 269 } 270 271 // Push pushes a plugin to the store. 272 func (pm *Manager) Push(ctx context.Context, name string, metaHeader http.Header, authConfig *types.AuthConfig, outStream io.Writer) error { 273 p, err := pm.config.Store.GetV2Plugin(name) 274 if err != nil { 275 return err 276 } 277 278 ref, err := reference.ParseNamed(p.Name()) 279 if err != nil { 280 return errors.Wrapf(err, "plugin has invalid name %v for push", p.Name()) 281 } 282 283 var po progress.Output 284 if outStream != nil { 285 // Include a buffer so that slow client connections don't affect 286 // transfer performance. 287 progressChan := make(chan progress.Progress, 100) 288 289 writesDone := make(chan struct{}) 290 291 defer func() { 292 close(progressChan) 293 <-writesDone 294 }() 295 296 var cancelFunc context.CancelFunc 297 ctx, cancelFunc = context.WithCancel(ctx) 298 299 go func() { 300 progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan) 301 close(writesDone) 302 }() 303 304 po = progress.ChanOutput(progressChan) 305 } else { 306 po = progress.DiscardOutput() 307 } 308 309 // TODO: replace these with manager 310 is := &pluginConfigStore{ 311 pm: pm, 312 plugin: p, 313 } 314 ls := &pluginLayerProvider{ 315 pm: pm, 316 plugin: p, 317 } 318 rs := &pluginReference{ 319 name: ref, 320 pluginID: p.Config, 321 } 322 323 uploadManager := xfer.NewLayerUploadManager(3) 324 325 imagePushConfig := &distribution.ImagePushConfig{ 326 Config: distribution.Config{ 327 MetaHeaders: metaHeader, 328 AuthConfig: authConfig, 329 ProgressOutput: po, 330 RegistryService: pm.config.RegistryService, 331 ReferenceStore: rs, 332 ImageEventLogger: pm.config.LogPluginEvent, 333 ImageStore: is, 334 RequireSchema2: true, 335 }, 336 ConfigMediaType: schema2.MediaTypePluginConfig, 337 LayerStore: ls, 338 UploadManager: uploadManager, 339 } 340 341 return distribution.Push(ctx, ref, imagePushConfig) 342 } 343 344 type pluginReference struct { 345 name reference.Named 346 pluginID digest.Digest 347 } 348 349 func (r *pluginReference) References(id digest.Digest) []reference.Named { 350 if r.pluginID != id { 351 return nil 352 } 353 return []reference.Named{r.name} 354 } 355 356 func (r *pluginReference) ReferencesByName(ref reference.Named) []reference.Association { 357 return []reference.Association{ 358 { 359 Ref: r.name, 360 ID: r.pluginID, 361 }, 362 } 363 } 364 365 func (r *pluginReference) Get(ref reference.Named) (digest.Digest, error) { 366 if r.name.String() != ref.String() { 367 return digest.Digest(""), reference.ErrDoesNotExist 368 } 369 return r.pluginID, nil 370 } 371 372 func (r *pluginReference) AddTag(ref reference.Named, id digest.Digest, force bool) error { 373 // Read only, ignore 374 return nil 375 } 376 func (r *pluginReference) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error { 377 // Read only, ignore 378 return nil 379 } 380 func (r *pluginReference) Delete(ref reference.Named) (bool, error) { 381 // Read only, ignore 382 return false, nil 383 } 384 385 type pluginConfigStore struct { 386 pm *Manager 387 plugin *v2.Plugin 388 } 389 390 func (s *pluginConfigStore) Put([]byte) (digest.Digest, error) { 391 return digest.Digest(""), errors.New("cannot store config on push") 392 } 393 394 func (s *pluginConfigStore) Get(d digest.Digest) ([]byte, error) { 395 if s.plugin.Config != d { 396 return nil, errors.New("plugin not found") 397 } 398 rwc, err := s.pm.blobStore.Get(d) 399 if err != nil { 400 return nil, err 401 } 402 defer rwc.Close() 403 return ioutil.ReadAll(rwc) 404 } 405 406 func (s *pluginConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) { 407 return configToRootFS(c) 408 } 409 410 type pluginLayerProvider struct { 411 pm *Manager 412 plugin *v2.Plugin 413 } 414 415 func (p *pluginLayerProvider) Get(id layer.ChainID) (distribution.PushLayer, error) { 416 rootFS := rootFSFromPlugin(p.plugin.PluginObj.Config.Rootfs) 417 var i int 418 for i = 1; i <= len(rootFS.DiffIDs); i++ { 419 if layer.CreateChainID(rootFS.DiffIDs[:i]) == id { 420 break 421 } 422 } 423 if i > len(rootFS.DiffIDs) { 424 return nil, errors.New("layer not found") 425 } 426 return &pluginLayer{ 427 pm: p.pm, 428 diffIDs: rootFS.DiffIDs[:i], 429 blobs: p.plugin.Blobsums[:i], 430 }, nil 431 } 432 433 type pluginLayer struct { 434 pm *Manager 435 diffIDs []layer.DiffID 436 blobs []digest.Digest 437 } 438 439 func (l *pluginLayer) ChainID() layer.ChainID { 440 return layer.CreateChainID(l.diffIDs) 441 } 442 443 func (l *pluginLayer) DiffID() layer.DiffID { 444 return l.diffIDs[len(l.diffIDs)-1] 445 } 446 447 func (l *pluginLayer) Parent() distribution.PushLayer { 448 if len(l.diffIDs) == 1 { 449 return nil 450 } 451 return &pluginLayer{ 452 pm: l.pm, 453 diffIDs: l.diffIDs[:len(l.diffIDs)-1], 454 blobs: l.blobs[:len(l.diffIDs)-1], 455 } 456 } 457 458 func (l *pluginLayer) Open() (io.ReadCloser, error) { 459 return l.pm.blobStore.Get(l.blobs[len(l.diffIDs)-1]) 460 } 461 462 func (l *pluginLayer) Size() (int64, error) { 463 return l.pm.blobStore.Size(l.blobs[len(l.diffIDs)-1]) 464 } 465 466 func (l *pluginLayer) MediaType() string { 467 return schema2.MediaTypeLayer 468 } 469 470 func (l *pluginLayer) Release() { 471 // Nothing needs to be release, no references held 472 } 473 474 // Remove deletes plugin's root directory. 475 func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error { 476 p, err := pm.config.Store.GetV2Plugin(name) 477 pm.mu.RLock() 478 c := pm.cMap[p] 479 pm.mu.RUnlock() 480 481 if err != nil { 482 return err 483 } 484 485 if !config.ForceRemove { 486 if p.GetRefCount() > 0 { 487 return fmt.Errorf("plugin %s is in use", p.Name()) 488 } 489 if p.IsEnabled() { 490 return fmt.Errorf("plugin %s is enabled", p.Name()) 491 } 492 } 493 494 if p.IsEnabled() { 495 if err := pm.disable(p, c); err != nil { 496 logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err) 497 } 498 } 499 500 defer func() { 501 go pm.GC() 502 }() 503 504 id := p.GetID() 505 pm.config.Store.Remove(p) 506 pluginDir := filepath.Join(pm.config.Root, id) 507 if err := os.RemoveAll(pluginDir); err != nil { 508 logrus.Warnf("unable to remove %q from plugin remove: %v", pluginDir, err) 509 } 510 pm.config.LogPluginEvent(id, name, "remove") 511 return nil 512 } 513 514 // Set sets plugin args 515 func (pm *Manager) Set(name string, args []string) error { 516 p, err := pm.config.Store.GetV2Plugin(name) 517 if err != nil { 518 return err 519 } 520 if err := p.Set(args); err != nil { 521 return err 522 } 523 return pm.save(p) 524 } 525 526 // CreateFromContext creates a plugin from the given pluginDir which contains 527 // both the rootfs and the config.json and a repoName with optional tag. 528 func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.ReadCloser, options *types.PluginCreateOptions) (err error) { 529 pm.muGC.RLock() 530 defer pm.muGC.RUnlock() 531 532 ref, err := reference.ParseNamed(options.RepoName) 533 if err != nil { 534 return errors.Wrapf(err, "failed to parse reference %v", options.RepoName) 535 } 536 if _, ok := ref.(reference.Canonical); ok { 537 return errors.Errorf("canonical references are not permitted") 538 } 539 name := reference.WithDefaultTag(ref).String() 540 541 if err := pm.config.Store.validateName(name); err != nil { // fast check, real check is in createPlugin() 542 return err 543 } 544 545 tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs") 546 defer os.RemoveAll(tmpRootFSDir) 547 if err != nil { 548 return errors.Wrap(err, "failed to create temp directory") 549 } 550 var configJSON []byte 551 rootFS := splitConfigRootFSFromTar(tarCtx, &configJSON) 552 553 rootFSBlob, err := pm.blobStore.New() 554 if err != nil { 555 return err 556 } 557 defer rootFSBlob.Close() 558 gzw := gzip.NewWriter(rootFSBlob) 559 layerDigester := digest.Canonical.Digester() 560 rootFSReader := io.TeeReader(rootFS, io.MultiWriter(gzw, layerDigester.Hash())) 561 562 if err := chrootarchive.Untar(rootFSReader, tmpRootFSDir, nil); err != nil { 563 return err 564 } 565 if err := rootFS.Close(); err != nil { 566 return err 567 } 568 569 if configJSON == nil { 570 return errors.New("config not found") 571 } 572 573 if err := gzw.Close(); err != nil { 574 return errors.Wrap(err, "error closing gzip writer") 575 } 576 577 var config types.PluginConfig 578 if err := json.Unmarshal(configJSON, &config); err != nil { 579 return errors.Wrap(err, "failed to parse config") 580 } 581 582 if err := pm.validateConfig(config); err != nil { 583 return err 584 } 585 586 pm.mu.Lock() 587 defer pm.mu.Unlock() 588 589 rootFSBlobsum, err := rootFSBlob.Commit() 590 if err != nil { 591 return err 592 } 593 defer func() { 594 if err != nil { 595 go pm.GC() 596 } 597 }() 598 599 config.Rootfs = &types.PluginConfigRootfs{ 600 Type: "layers", 601 DiffIds: []string{layerDigester.Digest().String()}, 602 } 603 604 configBlob, err := pm.blobStore.New() 605 if err != nil { 606 return err 607 } 608 defer configBlob.Close() 609 if err := json.NewEncoder(configBlob).Encode(config); err != nil { 610 return errors.Wrap(err, "error encoding json config") 611 } 612 configBlobsum, err := configBlob.Commit() 613 if err != nil { 614 return err 615 } 616 617 p, err := pm.createPlugin(name, configBlobsum, []digest.Digest{rootFSBlobsum}, tmpRootFSDir, nil) 618 if err != nil { 619 return err 620 } 621 622 pm.config.LogPluginEvent(p.PluginObj.ID, name, "create") 623 624 return nil 625 } 626 627 func (pm *Manager) validateConfig(config types.PluginConfig) error { 628 return nil // TODO: 629 } 630 631 func splitConfigRootFSFromTar(in io.ReadCloser, config *[]byte) io.ReadCloser { 632 pr, pw := io.Pipe() 633 go func() { 634 tarReader := tar.NewReader(in) 635 tarWriter := tar.NewWriter(pw) 636 defer in.Close() 637 638 hasRootFS := false 639 640 for { 641 hdr, err := tarReader.Next() 642 if err == io.EOF { 643 if !hasRootFS { 644 pw.CloseWithError(errors.Wrap(err, "no rootfs found")) 645 return 646 } 647 // Signals end of archive. 648 tarWriter.Close() 649 pw.Close() 650 return 651 } 652 if err != nil { 653 pw.CloseWithError(errors.Wrap(err, "failed to read from tar")) 654 return 655 } 656 657 content := io.Reader(tarReader) 658 name := path.Clean(hdr.Name) 659 if path.IsAbs(name) { 660 name = name[1:] 661 } 662 if name == configFileName { 663 dt, err := ioutil.ReadAll(content) 664 if err != nil { 665 pw.CloseWithError(errors.Wrapf(err, "failed to read %s", configFileName)) 666 return 667 } 668 *config = dt 669 } 670 if parts := strings.Split(name, "/"); len(parts) != 0 && parts[0] == rootFSFileName { 671 hdr.Name = path.Clean(path.Join(parts[1:]...)) 672 if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(strings.ToLower(hdr.Linkname), rootFSFileName+"/") { 673 hdr.Linkname = hdr.Linkname[len(rootFSFileName)+1:] 674 } 675 if err := tarWriter.WriteHeader(hdr); err != nil { 676 pw.CloseWithError(errors.Wrap(err, "error writing tar header")) 677 return 678 } 679 if _, err := pools.Copy(tarWriter, content); err != nil { 680 pw.CloseWithError(errors.Wrap(err, "error copying tar data")) 681 return 682 } 683 hasRootFS = true 684 } else { 685 io.Copy(ioutil.Discard, content) 686 } 687 } 688 }() 689 return pr 690 }