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