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