github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/plugin/backend_linux.go (about) 1 package plugin // import "github.com/docker/docker/plugin" 2 3 import ( 4 "archive/tar" 5 "compress/gzip" 6 "context" 7 "encoding/json" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "os" 12 "path" 13 "path/filepath" 14 "runtime" 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/errdefs" 26 "github.com/docker/docker/image" 27 "github.com/docker/docker/layer" 28 "github.com/docker/docker/pkg/authorization" 29 "github.com/docker/docker/pkg/chrootarchive" 30 "github.com/docker/docker/pkg/mount" 31 "github.com/docker/docker/pkg/pools" 32 "github.com/docker/docker/pkg/progress" 33 "github.com/docker/docker/pkg/system" 34 "github.com/docker/docker/plugin/v2" 35 refstore "github.com/docker/docker/reference" 36 "github.com/opencontainers/go-digest" 37 specs "github.com/opencontainers/image-spec/specs-go/v1" 38 "github.com/pkg/errors" 39 "github.com/sirupsen/logrus" 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 errors.WithStack(inUseError(p.Name())) 59 } 60 61 for _, typ := range p.GetTypes() { 62 if typ.Capability == authorization.AuthZApiImplements { 63 pm.config.AuthzMiddleware.RemovePlugin(p.Name()) 64 } 65 } 66 67 if err := pm.disable(p, c); err != nil { 68 return err 69 } 70 pm.publisher.Publish(EventDisable{Plugin: p.PluginObj}) 71 pm.config.LogPluginEvent(p.GetID(), refOrID, "disable") 72 return nil 73 } 74 75 // Enable activates a plugin, which implies that they are ready to be used by containers. 76 func (pm *Manager) Enable(refOrID string, config *types.PluginEnableConfig) error { 77 p, err := pm.config.Store.GetV2Plugin(refOrID) 78 if err != nil { 79 return err 80 } 81 82 c := &controller{timeoutInSecs: config.Timeout} 83 if err := pm.enable(p, c, false); err != nil { 84 return err 85 } 86 pm.publisher.Publish(EventEnable{Plugin: p.PluginObj}) 87 pm.config.LogPluginEvent(p.GetID(), refOrID, "enable") 88 return nil 89 } 90 91 // Inspect examines a plugin config 92 func (pm *Manager) Inspect(refOrID string) (tp *types.Plugin, err error) { 93 p, err := pm.config.Store.GetV2Plugin(refOrID) 94 if err != nil { 95 return nil, err 96 } 97 98 return &p.PluginObj, nil 99 } 100 101 func (pm *Manager) pull(ctx context.Context, ref reference.Named, config *distribution.ImagePullConfig, outStream io.Writer) error { 102 if outStream != nil { 103 // Include a buffer so that slow client connections don't affect 104 // transfer performance. 105 progressChan := make(chan progress.Progress, 100) 106 107 writesDone := make(chan struct{}) 108 109 defer func() { 110 close(progressChan) 111 <-writesDone 112 }() 113 114 var cancelFunc context.CancelFunc 115 ctx, cancelFunc = context.WithCancel(ctx) 116 117 go func() { 118 progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan) 119 close(writesDone) 120 }() 121 122 config.ProgressOutput = progress.ChanOutput(progressChan) 123 } else { 124 config.ProgressOutput = progress.DiscardOutput() 125 } 126 return distribution.Pull(ctx, ref, config) 127 } 128 129 type tempConfigStore struct { 130 config []byte 131 configDigest digest.Digest 132 } 133 134 func (s *tempConfigStore) Put(c []byte) (digest.Digest, error) { 135 dgst := digest.FromBytes(c) 136 137 s.config = c 138 s.configDigest = dgst 139 140 return dgst, nil 141 } 142 143 func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) { 144 if d != s.configDigest { 145 return nil, errNotFound("digest not found") 146 } 147 return s.config, nil 148 } 149 150 func (s *tempConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) { 151 return configToRootFS(c) 152 } 153 154 func (s *tempConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error) { 155 // TODO: LCOW/Plugins. This will need revisiting. For now use the runtime OS 156 return &specs.Platform{OS: runtime.GOOS}, nil 157 } 158 159 func computePrivileges(c types.PluginConfig) types.PluginPrivileges { 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 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, errdefs.System(err) 245 } 246 247 return computePrivileges(config), nil 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 err 255 } 256 257 if p.IsEnabled() { 258 return errors.Wrap(enabledError(p.Name()), "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(errdefs.InvalidParameter(err), "failed to parse %q", name) 267 } 268 269 tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs") 270 if err != nil { 271 return errors.Wrap(errdefs.System(err), "error preparing upgrade") 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, opts ...CreateOpt) (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(errdefs.InvalidParameter(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 errdefs.InvalidParameter(err) 319 } 320 321 tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs") 322 if err != nil { 323 return errors.Wrap(errdefs.System(err), "error preparing pull") 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 refOpt := func(p *v2.Plugin) { 351 p.PluginObj.PluginReference = ref.String() 352 } 353 optsList := make([]CreateOpt, 0, len(opts)+1) 354 optsList = append(optsList, opts...) 355 optsList = append(optsList, refOpt) 356 357 p, err := pm.createPlugin(name, dm.configDigest, dm.blobs, tmpRootFSDir, &privileges, optsList...) 358 if err != nil { 359 return err 360 } 361 362 pm.publisher.Publish(EventCreate{Plugin: p.PluginObj}) 363 return nil 364 } 365 366 // List displays the list of plugins and associated metadata. 367 func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) { 368 if err := pluginFilters.Validate(acceptedPluginFilterTags); err != nil { 369 return nil, err 370 } 371 372 enabledOnly := false 373 disabledOnly := false 374 if pluginFilters.Contains("enabled") { 375 if pluginFilters.ExactMatch("enabled", "true") { 376 enabledOnly = true 377 } else if pluginFilters.ExactMatch("enabled", "false") { 378 disabledOnly = true 379 } else { 380 return nil, invalidFilter{"enabled", pluginFilters.Get("enabled")} 381 } 382 } 383 384 plugins := pm.config.Store.GetAll() 385 out := make([]types.Plugin, 0, len(plugins)) 386 387 next: 388 for _, p := range plugins { 389 if enabledOnly && !p.PluginObj.Enabled { 390 continue 391 } 392 if disabledOnly && p.PluginObj.Enabled { 393 continue 394 } 395 if pluginFilters.Contains("capability") { 396 for _, f := range p.GetTypes() { 397 if !pluginFilters.Match("capability", f.Capability) { 398 continue next 399 } 400 } 401 } 402 out = append(out, p.PluginObj) 403 } 404 return out, nil 405 } 406 407 // Push pushes a plugin to the store. 408 func (pm *Manager) Push(ctx context.Context, name string, metaHeader http.Header, authConfig *types.AuthConfig, outStream io.Writer) error { 409 p, err := pm.config.Store.GetV2Plugin(name) 410 if err != nil { 411 return err 412 } 413 414 ref, err := reference.ParseNormalizedNamed(p.Name()) 415 if err != nil { 416 return errors.Wrapf(err, "plugin has invalid name %v for push", p.Name()) 417 } 418 419 var po progress.Output 420 if outStream != nil { 421 // Include a buffer so that slow client connections don't affect 422 // transfer performance. 423 progressChan := make(chan progress.Progress, 100) 424 425 writesDone := make(chan struct{}) 426 427 defer func() { 428 close(progressChan) 429 <-writesDone 430 }() 431 432 var cancelFunc context.CancelFunc 433 ctx, cancelFunc = context.WithCancel(ctx) 434 435 go func() { 436 progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan) 437 close(writesDone) 438 }() 439 440 po = progress.ChanOutput(progressChan) 441 } else { 442 po = progress.DiscardOutput() 443 } 444 445 // TODO: replace these with manager 446 is := &pluginConfigStore{ 447 pm: pm, 448 plugin: p, 449 } 450 lss := make(map[string]distribution.PushLayerProvider) 451 lss[runtime.GOOS] = &pluginLayerProvider{ 452 pm: pm, 453 plugin: p, 454 } 455 rs := &pluginReference{ 456 name: ref, 457 pluginID: p.Config, 458 } 459 460 uploadManager := xfer.NewLayerUploadManager(3) 461 462 imagePushConfig := &distribution.ImagePushConfig{ 463 Config: distribution.Config{ 464 MetaHeaders: metaHeader, 465 AuthConfig: authConfig, 466 ProgressOutput: po, 467 RegistryService: pm.config.RegistryService, 468 ReferenceStore: rs, 469 ImageEventLogger: pm.config.LogPluginEvent, 470 ImageStore: is, 471 RequireSchema2: true, 472 }, 473 ConfigMediaType: schema2.MediaTypePluginConfig, 474 LayerStores: lss, 475 UploadManager: uploadManager, 476 } 477 478 return distribution.Push(ctx, ref, imagePushConfig) 479 } 480 481 type pluginReference struct { 482 name reference.Named 483 pluginID digest.Digest 484 } 485 486 func (r *pluginReference) References(id digest.Digest) []reference.Named { 487 if r.pluginID != id { 488 return nil 489 } 490 return []reference.Named{r.name} 491 } 492 493 func (r *pluginReference) ReferencesByName(ref reference.Named) []refstore.Association { 494 return []refstore.Association{ 495 { 496 Ref: r.name, 497 ID: r.pluginID, 498 }, 499 } 500 } 501 502 func (r *pluginReference) Get(ref reference.Named) (digest.Digest, error) { 503 if r.name.String() != ref.String() { 504 return digest.Digest(""), refstore.ErrDoesNotExist 505 } 506 return r.pluginID, nil 507 } 508 509 func (r *pluginReference) AddTag(ref reference.Named, id digest.Digest, force bool) error { 510 // Read only, ignore 511 return nil 512 } 513 func (r *pluginReference) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error { 514 // Read only, ignore 515 return nil 516 } 517 func (r *pluginReference) Delete(ref reference.Named) (bool, error) { 518 // Read only, ignore 519 return false, nil 520 } 521 522 type pluginConfigStore struct { 523 pm *Manager 524 plugin *v2.Plugin 525 } 526 527 func (s *pluginConfigStore) Put([]byte) (digest.Digest, error) { 528 return digest.Digest(""), errors.New("cannot store config on push") 529 } 530 531 func (s *pluginConfigStore) Get(d digest.Digest) ([]byte, error) { 532 if s.plugin.Config != d { 533 return nil, errors.New("plugin not found") 534 } 535 rwc, err := s.pm.blobStore.Get(d) 536 if err != nil { 537 return nil, err 538 } 539 defer rwc.Close() 540 return ioutil.ReadAll(rwc) 541 } 542 543 func (s *pluginConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) { 544 return configToRootFS(c) 545 } 546 547 func (s *pluginConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error) { 548 // TODO: LCOW/Plugins. This will need revisiting. For now use the runtime OS 549 return &specs.Platform{OS: runtime.GOOS}, nil 550 } 551 552 type pluginLayerProvider struct { 553 pm *Manager 554 plugin *v2.Plugin 555 } 556 557 func (p *pluginLayerProvider) Get(id layer.ChainID) (distribution.PushLayer, error) { 558 rootFS := rootFSFromPlugin(p.plugin.PluginObj.Config.Rootfs) 559 var i int 560 for i = 1; i <= len(rootFS.DiffIDs); i++ { 561 if layer.CreateChainID(rootFS.DiffIDs[:i]) == id { 562 break 563 } 564 } 565 if i > len(rootFS.DiffIDs) { 566 return nil, errors.New("layer not found") 567 } 568 return &pluginLayer{ 569 pm: p.pm, 570 diffIDs: rootFS.DiffIDs[:i], 571 blobs: p.plugin.Blobsums[:i], 572 }, nil 573 } 574 575 type pluginLayer struct { 576 pm *Manager 577 diffIDs []layer.DiffID 578 blobs []digest.Digest 579 } 580 581 func (l *pluginLayer) ChainID() layer.ChainID { 582 return layer.CreateChainID(l.diffIDs) 583 } 584 585 func (l *pluginLayer) DiffID() layer.DiffID { 586 return l.diffIDs[len(l.diffIDs)-1] 587 } 588 589 func (l *pluginLayer) Parent() distribution.PushLayer { 590 if len(l.diffIDs) == 1 { 591 return nil 592 } 593 return &pluginLayer{ 594 pm: l.pm, 595 diffIDs: l.diffIDs[:len(l.diffIDs)-1], 596 blobs: l.blobs[:len(l.diffIDs)-1], 597 } 598 } 599 600 func (l *pluginLayer) Open() (io.ReadCloser, error) { 601 return l.pm.blobStore.Get(l.blobs[len(l.diffIDs)-1]) 602 } 603 604 func (l *pluginLayer) Size() (int64, error) { 605 return l.pm.blobStore.Size(l.blobs[len(l.diffIDs)-1]) 606 } 607 608 func (l *pluginLayer) MediaType() string { 609 return schema2.MediaTypeLayer 610 } 611 612 func (l *pluginLayer) Release() { 613 // Nothing needs to be release, no references held 614 } 615 616 // Remove deletes plugin's root directory. 617 func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error { 618 p, err := pm.config.Store.GetV2Plugin(name) 619 pm.mu.RLock() 620 c := pm.cMap[p] 621 pm.mu.RUnlock() 622 623 if err != nil { 624 return err 625 } 626 627 if !config.ForceRemove { 628 if p.GetRefCount() > 0 { 629 return inUseError(p.Name()) 630 } 631 if p.IsEnabled() { 632 return enabledError(p.Name()) 633 } 634 } 635 636 if p.IsEnabled() { 637 if err := pm.disable(p, c); err != nil { 638 logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err) 639 } 640 } 641 642 defer func() { 643 go pm.GC() 644 }() 645 646 id := p.GetID() 647 pluginDir := filepath.Join(pm.config.Root, id) 648 649 if err := mount.RecursiveUnmount(pluginDir); err != nil { 650 return errors.Wrap(err, "error unmounting plugin data") 651 } 652 653 if err := atomicRemoveAll(pluginDir); err != nil { 654 return err 655 } 656 657 pm.config.Store.Remove(p) 658 pm.config.LogPluginEvent(id, name, "remove") 659 pm.publisher.Publish(EventRemove{Plugin: p.PluginObj}) 660 return nil 661 } 662 663 // Set sets plugin args 664 func (pm *Manager) Set(name string, args []string) error { 665 p, err := pm.config.Store.GetV2Plugin(name) 666 if err != nil { 667 return err 668 } 669 if err := p.Set(args); err != nil { 670 return err 671 } 672 return pm.save(p) 673 } 674 675 // CreateFromContext creates a plugin from the given pluginDir which contains 676 // both the rootfs and the config.json and a repoName with optional tag. 677 func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.ReadCloser, options *types.PluginCreateOptions) (err error) { 678 pm.muGC.RLock() 679 defer pm.muGC.RUnlock() 680 681 ref, err := reference.ParseNormalizedNamed(options.RepoName) 682 if err != nil { 683 return errors.Wrapf(err, "failed to parse reference %v", options.RepoName) 684 } 685 if _, ok := ref.(reference.Canonical); ok { 686 return errors.Errorf("canonical references are not permitted") 687 } 688 name := reference.FamiliarString(reference.TagNameOnly(ref)) 689 690 if err := pm.config.Store.validateName(name); err != nil { // fast check, real check is in createPlugin() 691 return err 692 } 693 694 tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs") 695 if err != nil { 696 return errors.Wrap(err, "failed to create temp directory") 697 } 698 defer os.RemoveAll(tmpRootFSDir) 699 700 var configJSON []byte 701 rootFS := splitConfigRootFSFromTar(tarCtx, &configJSON) 702 703 rootFSBlob, err := pm.blobStore.New() 704 if err != nil { 705 return err 706 } 707 defer rootFSBlob.Close() 708 gzw := gzip.NewWriter(rootFSBlob) 709 layerDigester := digest.Canonical.Digester() 710 rootFSReader := io.TeeReader(rootFS, io.MultiWriter(gzw, layerDigester.Hash())) 711 712 if err := chrootarchive.Untar(rootFSReader, tmpRootFSDir, nil); err != nil { 713 return err 714 } 715 if err := rootFS.Close(); err != nil { 716 return err 717 } 718 719 if configJSON == nil { 720 return errors.New("config not found") 721 } 722 723 if err := gzw.Close(); err != nil { 724 return errors.Wrap(err, "error closing gzip writer") 725 } 726 727 var config types.PluginConfig 728 if err := json.Unmarshal(configJSON, &config); err != nil { 729 return errors.Wrap(err, "failed to parse config") 730 } 731 732 if err := pm.validateConfig(config); err != nil { 733 return err 734 } 735 736 pm.mu.Lock() 737 defer pm.mu.Unlock() 738 739 rootFSBlobsum, err := rootFSBlob.Commit() 740 if err != nil { 741 return err 742 } 743 defer func() { 744 if err != nil { 745 go pm.GC() 746 } 747 }() 748 749 config.Rootfs = &types.PluginConfigRootfs{ 750 Type: "layers", 751 DiffIds: []string{layerDigester.Digest().String()}, 752 } 753 754 config.DockerVersion = dockerversion.Version 755 756 configBlob, err := pm.blobStore.New() 757 if err != nil { 758 return err 759 } 760 defer configBlob.Close() 761 if err := json.NewEncoder(configBlob).Encode(config); err != nil { 762 return errors.Wrap(err, "error encoding json config") 763 } 764 configBlobsum, err := configBlob.Commit() 765 if err != nil { 766 return err 767 } 768 769 p, err := pm.createPlugin(name, configBlobsum, []digest.Digest{rootFSBlobsum}, tmpRootFSDir, nil) 770 if err != nil { 771 return err 772 } 773 p.PluginObj.PluginReference = name 774 775 pm.publisher.Publish(EventCreate{Plugin: p.PluginObj}) 776 pm.config.LogPluginEvent(p.PluginObj.ID, name, "create") 777 778 return nil 779 } 780 781 func (pm *Manager) validateConfig(config types.PluginConfig) error { 782 return nil // TODO: 783 } 784 785 func splitConfigRootFSFromTar(in io.ReadCloser, config *[]byte) io.ReadCloser { 786 pr, pw := io.Pipe() 787 go func() { 788 tarReader := tar.NewReader(in) 789 tarWriter := tar.NewWriter(pw) 790 defer in.Close() 791 792 hasRootFS := false 793 794 for { 795 hdr, err := tarReader.Next() 796 if err == io.EOF { 797 if !hasRootFS { 798 pw.CloseWithError(errors.Wrap(err, "no rootfs found")) 799 return 800 } 801 // Signals end of archive. 802 tarWriter.Close() 803 pw.Close() 804 return 805 } 806 if err != nil { 807 pw.CloseWithError(errors.Wrap(err, "failed to read from tar")) 808 return 809 } 810 811 content := io.Reader(tarReader) 812 name := path.Clean(hdr.Name) 813 if path.IsAbs(name) { 814 name = name[1:] 815 } 816 if name == configFileName { 817 dt, err := ioutil.ReadAll(content) 818 if err != nil { 819 pw.CloseWithError(errors.Wrapf(err, "failed to read %s", configFileName)) 820 return 821 } 822 *config = dt 823 } 824 if parts := strings.Split(name, "/"); len(parts) != 0 && parts[0] == rootFSFileName { 825 hdr.Name = path.Clean(path.Join(parts[1:]...)) 826 if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(strings.ToLower(hdr.Linkname), rootFSFileName+"/") { 827 hdr.Linkname = hdr.Linkname[len(rootFSFileName)+1:] 828 } 829 if err := tarWriter.WriteHeader(hdr); err != nil { 830 pw.CloseWithError(errors.Wrap(err, "error writing tar header")) 831 return 832 } 833 if _, err := pools.Copy(tarWriter, content); err != nil { 834 pw.CloseWithError(errors.Wrap(err, "error copying tar data")) 835 return 836 } 837 hasRootFS = true 838 } else { 839 io.Copy(ioutil.Discard, content) 840 } 841 } 842 }() 843 return pr 844 } 845 846 func atomicRemoveAll(dir string) error { 847 renamed := dir + "-removing" 848 849 err := os.Rename(dir, renamed) 850 switch { 851 case os.IsNotExist(err), err == nil: 852 // even if `dir` doesn't exist, we can still try and remove `renamed` 853 case os.IsExist(err): 854 // Some previous remove failed, check if the origin dir exists 855 if e := system.EnsureRemoveAll(renamed); e != nil { 856 return errors.Wrap(err, "rename target already exists and could not be removed") 857 } 858 if _, err := os.Stat(dir); os.IsNotExist(err) { 859 // origin doesn't exist, nothing left to do 860 return nil 861 } 862 863 // attempt to rename again 864 if err := os.Rename(dir, renamed); err != nil { 865 return errors.Wrap(err, "failed to rename dir for atomic removal") 866 } 867 default: 868 return errors.Wrap(err, "failed to rename dir for atomic removal") 869 } 870 871 if err := system.EnsureRemoveAll(renamed); err != nil { 872 os.Rename(renamed, dir) 873 return err 874 } 875 return nil 876 }