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