github.com/rish1988/moby@v25.0.2+incompatible/plugin/backend_linux.go (about) 1 package plugin // import "github.com/docker/docker/plugin" 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "compress/gzip" 7 "context" 8 "encoding/json" 9 "io" 10 "net/http" 11 "os" 12 "path" 13 "path/filepath" 14 "strings" 15 "time" 16 17 "github.com/containerd/containerd/content" 18 "github.com/containerd/containerd/images" 19 "github.com/containerd/containerd/platforms" 20 "github.com/containerd/containerd/remotes" 21 "github.com/containerd/containerd/remotes/docker" 22 "github.com/containerd/log" 23 "github.com/distribution/reference" 24 "github.com/docker/distribution/manifest/schema2" 25 "github.com/docker/docker/api/types" 26 "github.com/docker/docker/api/types/backend" 27 "github.com/docker/docker/api/types/events" 28 "github.com/docker/docker/api/types/filters" 29 "github.com/docker/docker/api/types/registry" 30 "github.com/docker/docker/dockerversion" 31 "github.com/docker/docker/errdefs" 32 "github.com/docker/docker/pkg/authorization" 33 "github.com/docker/docker/pkg/chrootarchive" 34 "github.com/docker/docker/pkg/containerfs" 35 "github.com/docker/docker/pkg/pools" 36 "github.com/docker/docker/pkg/progress" 37 "github.com/docker/docker/pkg/stringid" 38 v2 "github.com/docker/docker/plugin/v2" 39 "github.com/moby/sys/mount" 40 "github.com/opencontainers/go-digest" 41 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 42 "github.com/pkg/errors" 43 ) 44 45 var acceptedPluginFilterTags = map[string]bool{ 46 "enabled": true, 47 "capability": true, 48 } 49 50 // Disable deactivates a plugin. This means resources (volumes, networks) cant use them. 51 func (pm *Manager) Disable(refOrID string, config *backend.PluginDisableConfig) error { 52 p, err := pm.config.Store.GetV2Plugin(refOrID) 53 if err != nil { 54 return err 55 } 56 pm.mu.RLock() 57 c := pm.cMap[p] 58 pm.mu.RUnlock() 59 60 if !config.ForceDisable && p.GetRefCount() > 0 { 61 return errors.WithStack(inUseError(p.Name())) 62 } 63 64 for _, typ := range p.GetTypes() { 65 if typ.Capability == authorization.AuthZApiImplements { 66 pm.config.AuthzMiddleware.RemovePlugin(p.Name()) 67 } 68 } 69 70 if err := pm.disable(p, c); err != nil { 71 return err 72 } 73 pm.publisher.Publish(EventDisable{Plugin: p.PluginObj}) 74 pm.config.LogPluginEvent(p.GetID(), refOrID, events.ActionDisable) 75 return nil 76 } 77 78 // Enable activates a plugin, which implies that they are ready to be used by containers. 79 func (pm *Manager) Enable(refOrID string, config *backend.PluginEnableConfig) error { 80 p, err := pm.config.Store.GetV2Plugin(refOrID) 81 if err != nil { 82 return err 83 } 84 85 c := &controller{timeoutInSecs: config.Timeout} 86 if err := pm.enable(p, c, false); err != nil { 87 return err 88 } 89 pm.publisher.Publish(EventEnable{Plugin: p.PluginObj}) 90 pm.config.LogPluginEvent(p.GetID(), refOrID, events.ActionEnable) 91 return nil 92 } 93 94 // Inspect examines a plugin config 95 func (pm *Manager) Inspect(refOrID string) (tp *types.Plugin, err error) { 96 p, err := pm.config.Store.GetV2Plugin(refOrID) 97 if err != nil { 98 return nil, err 99 } 100 101 return &p.PluginObj, nil 102 } 103 104 func computePrivileges(c types.PluginConfig) types.PluginPrivileges { 105 var privileges types.PluginPrivileges 106 if c.Network.Type != "null" && c.Network.Type != "bridge" && c.Network.Type != "" { 107 privileges = append(privileges, types.PluginPrivilege{ 108 Name: "network", 109 Description: "permissions to access a network", 110 Value: []string{c.Network.Type}, 111 }) 112 } 113 if c.IpcHost { 114 privileges = append(privileges, types.PluginPrivilege{ 115 Name: "host ipc namespace", 116 Description: "allow access to host ipc namespace", 117 Value: []string{"true"}, 118 }) 119 } 120 if c.PidHost { 121 privileges = append(privileges, types.PluginPrivilege{ 122 Name: "host pid namespace", 123 Description: "allow access to host pid namespace", 124 Value: []string{"true"}, 125 }) 126 } 127 for _, mnt := range c.Mounts { 128 if mnt.Source != nil { 129 privileges = append(privileges, types.PluginPrivilege{ 130 Name: "mount", 131 Description: "host path to mount", 132 Value: []string{*mnt.Source}, 133 }) 134 } 135 } 136 for _, device := range c.Linux.Devices { 137 if device.Path != nil { 138 privileges = append(privileges, types.PluginPrivilege{ 139 Name: "device", 140 Description: "host device to access", 141 Value: []string{*device.Path}, 142 }) 143 } 144 } 145 if c.Linux.AllowAllDevices { 146 privileges = append(privileges, types.PluginPrivilege{ 147 Name: "allow-all-devices", 148 Description: "allow 'rwm' access to all devices", 149 Value: []string{"true"}, 150 }) 151 } 152 if len(c.Linux.Capabilities) > 0 { 153 privileges = append(privileges, types.PluginPrivilege{ 154 Name: "capabilities", 155 Description: "list of additional capabilities required", 156 Value: c.Linux.Capabilities, 157 }) 158 } 159 160 return privileges 161 } 162 163 // Privileges pulls a plugin config and computes the privileges required to install it. 164 func (pm *Manager) Privileges(ctx context.Context, ref reference.Named, metaHeader http.Header, authConfig *registry.AuthConfig) (types.PluginPrivileges, error) { 165 var ( 166 config types.PluginConfig 167 configSeen bool 168 ) 169 170 h := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 171 switch desc.MediaType { 172 case schema2.MediaTypeManifest, ocispec.MediaTypeImageManifest: 173 data, err := content.ReadBlob(ctx, pm.blobStore, desc) 174 if err != nil { 175 return nil, errors.Wrapf(err, "error reading image manifest from blob store for %s", ref) 176 } 177 178 var m ocispec.Manifest 179 if err := json.Unmarshal(data, &m); err != nil { 180 return nil, errors.Wrapf(err, "error unmarshaling image manifest for %s", ref) 181 } 182 return []ocispec.Descriptor{m.Config}, nil 183 case schema2.MediaTypePluginConfig: 184 configSeen = true 185 data, err := content.ReadBlob(ctx, pm.blobStore, desc) 186 if err != nil { 187 return nil, errors.Wrapf(err, "error reading plugin config from blob store for %s", ref) 188 } 189 190 if err := json.Unmarshal(data, &config); err != nil { 191 return nil, errors.Wrapf(err, "error unmarshaling plugin config for %s", ref) 192 } 193 } 194 195 return nil, nil 196 } 197 198 if err := pm.fetch(ctx, ref, authConfig, progress.DiscardOutput(), metaHeader, images.HandlerFunc(h)); err != nil { 199 return types.PluginPrivileges{}, nil 200 } 201 202 if !configSeen { 203 return types.PluginPrivileges{}, errors.Errorf("did not find plugin config for specified reference %s", ref) 204 } 205 206 return computePrivileges(config), nil 207 } 208 209 // Upgrade upgrades a plugin 210 // 211 // TODO: replace reference package usage with simpler url.Parse semantics 212 func (pm *Manager) Upgrade(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *registry.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer) (err error) { 213 p, err := pm.config.Store.GetV2Plugin(name) 214 if err != nil { 215 return err 216 } 217 218 if p.IsEnabled() { 219 return errors.Wrap(enabledError(p.Name()), "plugin must be disabled before upgrading") 220 } 221 222 // revalidate because Pull is public 223 if _, err := reference.ParseNormalizedNamed(name); err != nil { 224 return errors.Wrapf(errdefs.InvalidParameter(err), "failed to parse %q", name) 225 } 226 227 pm.muGC.RLock() 228 defer pm.muGC.RUnlock() 229 230 tmpRootFSDir, err := os.MkdirTemp(pm.tmpDir(), ".rootfs") 231 if err != nil { 232 return errors.Wrap(err, "error creating tmp dir for plugin rootfs") 233 } 234 235 var md fetchMeta 236 237 ctx, cancel := context.WithCancel(ctx) 238 out, waitProgress := setupProgressOutput(outStream, cancel) 239 defer waitProgress() 240 241 if err := pm.fetch(ctx, ref, authConfig, out, metaHeader, storeFetchMetadata(&md), childrenHandler(pm.blobStore), applyLayer(pm.blobStore, tmpRootFSDir, out)); err != nil { 242 return err 243 } 244 pm.config.LogPluginEvent(reference.FamiliarString(ref), name, events.ActionPull) 245 246 if err := validateFetchedMetadata(md); err != nil { 247 return err 248 } 249 250 if err := pm.upgradePlugin(p, md.config, md.manifest, md.blobs, tmpRootFSDir, &privileges); err != nil { 251 return err 252 } 253 p.PluginObj.PluginReference = ref.String() 254 return nil 255 } 256 257 // Pull pulls a plugin, check if the correct privileges are provided and install the plugin. 258 // 259 // TODO: replace reference package usage with simpler url.Parse semantics 260 func (pm *Manager) Pull(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *registry.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer, opts ...CreateOpt) (err error) { 261 pm.muGC.RLock() 262 defer pm.muGC.RUnlock() 263 264 // revalidate because Pull is public 265 nameref, err := reference.ParseNormalizedNamed(name) 266 if err != nil { 267 return errors.Wrapf(errdefs.InvalidParameter(err), "failed to parse %q", name) 268 } 269 name = reference.FamiliarString(reference.TagNameOnly(nameref)) 270 271 if err := pm.config.Store.validateName(name); err != nil { 272 return errdefs.InvalidParameter(err) 273 } 274 275 tmpRootFSDir, err := os.MkdirTemp(pm.tmpDir(), ".rootfs") 276 if err != nil { 277 return errors.Wrap(errdefs.System(err), "error preparing upgrade") 278 } 279 defer os.RemoveAll(tmpRootFSDir) 280 281 var md fetchMeta 282 283 ctx, cancel := context.WithCancel(ctx) 284 out, waitProgress := setupProgressOutput(outStream, cancel) 285 defer waitProgress() 286 287 if err := pm.fetch(ctx, ref, authConfig, out, metaHeader, storeFetchMetadata(&md), childrenHandler(pm.blobStore), applyLayer(pm.blobStore, tmpRootFSDir, out)); err != nil { 288 return err 289 } 290 pm.config.LogPluginEvent(reference.FamiliarString(ref), name, events.ActionPull) 291 292 if err := validateFetchedMetadata(md); err != nil { 293 return err 294 } 295 296 refOpt := func(p *v2.Plugin) { 297 p.PluginObj.PluginReference = ref.String() 298 } 299 optsList := make([]CreateOpt, 0, len(opts)+1) 300 optsList = append(optsList, opts...) 301 optsList = append(optsList, refOpt) 302 303 // TODO: tmpRootFSDir is empty but should have layers in it 304 p, err := pm.createPlugin(name, md.config, md.manifest, md.blobs, tmpRootFSDir, &privileges, optsList...) 305 if err != nil { 306 return err 307 } 308 309 pm.publisher.Publish(EventCreate{Plugin: p.PluginObj}) 310 311 return nil 312 } 313 314 // List displays the list of plugins and associated metadata. 315 func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) { 316 if err := pluginFilters.Validate(acceptedPluginFilterTags); err != nil { 317 return nil, err 318 } 319 320 enabledOnly := false 321 disabledOnly := false 322 if pluginFilters.Contains("enabled") { 323 enabledFilter, err := pluginFilters.GetBoolOrDefault("enabled", false) 324 if err != nil { 325 return nil, err 326 } 327 328 if enabledFilter { 329 enabledOnly = true 330 } else { 331 disabledOnly = true 332 } 333 } 334 335 plugins := pm.config.Store.GetAll() 336 out := make([]types.Plugin, 0, len(plugins)) 337 338 next: 339 for _, p := range plugins { 340 if enabledOnly && !p.PluginObj.Enabled { 341 continue 342 } 343 if disabledOnly && p.PluginObj.Enabled { 344 continue 345 } 346 if pluginFilters.Contains("capability") { 347 for _, f := range p.GetTypes() { 348 if !pluginFilters.Match("capability", f.Capability) { 349 continue next 350 } 351 } 352 } 353 out = append(out, p.PluginObj) 354 } 355 return out, nil 356 } 357 358 // Push pushes a plugin to the registry. 359 func (pm *Manager) Push(ctx context.Context, name string, metaHeader http.Header, authConfig *registry.AuthConfig, outStream io.Writer) error { 360 p, err := pm.config.Store.GetV2Plugin(name) 361 if err != nil { 362 return err 363 } 364 365 ref, err := reference.ParseNormalizedNamed(p.Name()) 366 if err != nil { 367 return errors.Wrapf(err, "plugin has invalid name %v for push", p.Name()) 368 } 369 370 statusTracker := docker.NewInMemoryTracker() 371 372 resolver, err := pm.newResolver(ctx, statusTracker, authConfig, metaHeader, false) 373 if err != nil { 374 return err 375 } 376 377 pusher, err := resolver.Pusher(ctx, ref.String()) 378 if err != nil { 379 return errors.Wrap(err, "error creating plugin pusher") 380 } 381 382 pj := newPushJobs(statusTracker) 383 384 ctx, cancel := context.WithCancel(ctx) 385 out, waitProgress := setupProgressOutput(outStream, cancel) 386 defer waitProgress() 387 388 progressHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 389 log.G(ctx).WithField("mediaType", desc.MediaType).WithField("digest", desc.Digest.String()).Debug("Preparing to push plugin layer") 390 id := stringid.TruncateID(desc.Digest.String()) 391 pj.add(remotes.MakeRefKey(ctx, desc), id) 392 progress.Update(out, id, "Preparing") 393 return nil, nil 394 }) 395 396 desc, err := pm.getManifestDescriptor(ctx, p) 397 if err != nil { 398 return errors.Wrap(err, "error reading plugin manifest") 399 } 400 401 progress.Messagef(out, "", "The push refers to repository [%s]", reference.FamiliarName(ref)) 402 403 // TODO: If a layer already exists on the registry, the progress output just says "Preparing" 404 go func() { 405 timer := time.NewTimer(100 * time.Millisecond) 406 defer timer.Stop() 407 if !timer.Stop() { 408 <-timer.C 409 } 410 var statuses []contentStatus 411 for { 412 timer.Reset(100 * time.Millisecond) 413 select { 414 case <-ctx.Done(): 415 return 416 case <-timer.C: 417 statuses = pj.status() 418 } 419 420 for _, s := range statuses { 421 out.WriteProgress(progress.Progress{ID: s.Ref, Current: s.Offset, Total: s.Total, Action: s.Status, LastUpdate: s.Offset == s.Total}) 422 } 423 } 424 }() 425 426 // Make sure we can authenticate the request since the auth scope for plugin repos is different than a normal repo. 427 ctx = docker.WithScope(ctx, scope(ref, true)) 428 if err := remotes.PushContent(ctx, pusher, desc, pm.blobStore, nil, nil, func(h images.Handler) images.Handler { 429 return images.Handlers(progressHandler, h) 430 }); err != nil { 431 // Try fallback to http. 432 // This is needed because the containerd pusher will only attempt the first registry config we pass, which would 433 // typically be https. 434 // If there are no http-only host configs found we'll error out anyway. 435 resolver, _ := pm.newResolver(ctx, statusTracker, authConfig, metaHeader, true) 436 if resolver != nil { 437 pusher, _ := resolver.Pusher(ctx, ref.String()) 438 if pusher != nil { 439 log.G(ctx).WithField("ref", ref).Debug("Re-attmpting push with http-fallback") 440 err2 := remotes.PushContent(ctx, pusher, desc, pm.blobStore, nil, nil, func(h images.Handler) images.Handler { 441 return images.Handlers(progressHandler, h) 442 }) 443 if err2 == nil { 444 err = nil 445 } else { 446 log.G(ctx).WithError(err2).WithField("ref", ref).Debug("Error while attempting push with http-fallback") 447 } 448 } 449 } 450 if err != nil { 451 return errors.Wrap(err, "error pushing plugin") 452 } 453 } 454 455 // For blobs that already exist in the registry we need to make sure to update the progress otherwise it will just say "pending" 456 // TODO: How to check if the layer already exists? Is it worth it? 457 for _, j := range pj.jobs { 458 progress.Update(out, pj.names[j], "Upload complete") 459 } 460 461 // Signal the client for content trust verification 462 progress.Aux(out, types.PushResult{Tag: ref.(reference.Tagged).Tag(), Digest: desc.Digest.String(), Size: int(desc.Size)}) 463 464 return nil 465 } 466 467 // manifest wraps an OCI manifest, because... 468 // Historically the registry does not support plugins unless the media type on the manifest is specifically schema2.MediaTypeManifest 469 // So the OCI manifest media type is not supported. 470 // Additionally, there is extra validation for the docker schema2 manifest than there is a mediatype set on the manifest itself 471 // even though this is set on the descriptor 472 // The OCI types do not have this field. 473 type manifest struct { 474 ocispec.Manifest 475 MediaType string `json:"mediaType,omitempty"` 476 } 477 478 func buildManifest(ctx context.Context, s content.Manager, config digest.Digest, layers []digest.Digest) (manifest, error) { 479 var m manifest 480 m.MediaType = images.MediaTypeDockerSchema2Manifest 481 m.SchemaVersion = 2 482 483 configInfo, err := s.Info(ctx, config) 484 if err != nil { 485 return m, errors.Wrapf(err, "error reading plugin config content for digest %s", config) 486 } 487 m.Config = ocispec.Descriptor{ 488 MediaType: mediaTypePluginConfig, 489 Size: configInfo.Size, 490 Digest: configInfo.Digest, 491 } 492 493 for _, l := range layers { 494 info, err := s.Info(ctx, l) 495 if err != nil { 496 return m, errors.Wrapf(err, "error fetching info for content digest %s", l) 497 } 498 m.Layers = append(m.Layers, ocispec.Descriptor{ 499 MediaType: images.MediaTypeDockerSchema2LayerGzip, // TODO: This is assuming everything is a gzip compressed layer, but that may not be true. 500 Digest: l, 501 Size: info.Size, 502 }) 503 } 504 return m, nil 505 } 506 507 // getManifestDescriptor gets the OCI descriptor for a manifest 508 // It will generate a manifest if one does not exist 509 func (pm *Manager) getManifestDescriptor(ctx context.Context, p *v2.Plugin) (ocispec.Descriptor, error) { 510 logger := log.G(ctx).WithField("plugin", p.Name()).WithField("digest", p.Manifest) 511 if p.Manifest != "" { 512 info, err := pm.blobStore.Info(ctx, p.Manifest) 513 if err == nil { 514 desc := ocispec.Descriptor{ 515 Size: info.Size, 516 Digest: info.Digest, 517 MediaType: images.MediaTypeDockerSchema2Manifest, 518 } 519 return desc, nil 520 } 521 logger.WithError(err).Debug("Could not find plugin manifest in content store") 522 } else { 523 logger.Info("Plugin does not have manifest digest") 524 } 525 logger.Info("Building a new plugin manifest") 526 527 manifest, err := buildManifest(ctx, pm.blobStore, p.Config, p.Blobsums) 528 if err != nil { 529 return ocispec.Descriptor{}, err 530 } 531 532 desc, err := writeManifest(ctx, pm.blobStore, &manifest) 533 if err != nil { 534 return desc, err 535 } 536 537 if err := pm.save(p); err != nil { 538 logger.WithError(err).Error("Could not save plugin with manifest digest") 539 } 540 return desc, nil 541 } 542 543 func writeManifest(ctx context.Context, cs content.Store, m *manifest) (ocispec.Descriptor, error) { 544 platform := platforms.DefaultSpec() 545 desc := ocispec.Descriptor{ 546 MediaType: images.MediaTypeDockerSchema2Manifest, 547 Platform: &platform, 548 } 549 data, err := json.Marshal(m) 550 if err != nil { 551 return desc, errors.Wrap(err, "error encoding manifest") 552 } 553 desc.Digest = digest.FromBytes(data) 554 desc.Size = int64(len(data)) 555 556 if err := content.WriteBlob(ctx, cs, remotes.MakeRefKey(ctx, desc), bytes.NewReader(data), desc); err != nil { 557 return desc, errors.Wrap(err, "error writing plugin manifest") 558 } 559 return desc, nil 560 } 561 562 // Remove deletes plugin's root directory. 563 func (pm *Manager) Remove(name string, config *backend.PluginRmConfig) error { 564 p, err := pm.config.Store.GetV2Plugin(name) 565 pm.mu.RLock() 566 c := pm.cMap[p] 567 pm.mu.RUnlock() 568 569 if err != nil { 570 return err 571 } 572 573 if !config.ForceRemove { 574 if p.GetRefCount() > 0 { 575 return inUseError(p.Name()) 576 } 577 if p.IsEnabled() { 578 return enabledError(p.Name()) 579 } 580 } 581 582 if p.IsEnabled() { 583 if err := pm.disable(p, c); err != nil { 584 log.G(context.TODO()).Errorf("failed to disable plugin '%s': %s", p.Name(), err) 585 } 586 } 587 588 defer func() { 589 go pm.GC() 590 }() 591 592 id := p.GetID() 593 pluginDir := filepath.Join(pm.config.Root, id) 594 595 if err := mount.RecursiveUnmount(pluginDir); err != nil { 596 return errors.Wrap(err, "error unmounting plugin data") 597 } 598 599 if err := atomicRemoveAll(pluginDir); err != nil { 600 return err 601 } 602 603 pm.config.Store.Remove(p) 604 pm.config.LogPluginEvent(id, name, events.ActionRemove) 605 pm.publisher.Publish(EventRemove{Plugin: p.PluginObj}) 606 return nil 607 } 608 609 // Set sets plugin args 610 func (pm *Manager) Set(name string, args []string) error { 611 p, err := pm.config.Store.GetV2Plugin(name) 612 if err != nil { 613 return err 614 } 615 if err := p.Set(args); err != nil { 616 return err 617 } 618 return pm.save(p) 619 } 620 621 // CreateFromContext creates a plugin from the given pluginDir which contains 622 // both the rootfs and the config.json and a repoName with optional tag. 623 func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.ReadCloser, options *types.PluginCreateOptions) (err error) { 624 pm.muGC.RLock() 625 defer pm.muGC.RUnlock() 626 627 ref, err := reference.ParseNormalizedNamed(options.RepoName) 628 if err != nil { 629 return errors.Wrapf(err, "failed to parse reference %v", options.RepoName) 630 } 631 if _, ok := ref.(reference.Canonical); ok { 632 return errors.Errorf("canonical references are not permitted") 633 } 634 name := reference.FamiliarString(reference.TagNameOnly(ref)) 635 636 if err := pm.config.Store.validateName(name); err != nil { // fast check, real check is in createPlugin() 637 return err 638 } 639 640 tmpRootFSDir, err := os.MkdirTemp(pm.tmpDir(), ".rootfs") 641 if err != nil { 642 return errors.Wrap(err, "failed to create temp directory") 643 } 644 defer os.RemoveAll(tmpRootFSDir) 645 646 var configJSON []byte 647 rootFS := splitConfigRootFSFromTar(tarCtx, &configJSON) 648 649 rootFSBlob, err := pm.blobStore.Writer(ctx, content.WithRef(name)) 650 if err != nil { 651 return err 652 } 653 defer rootFSBlob.Close() 654 655 gzw := gzip.NewWriter(rootFSBlob) 656 rootFSReader := io.TeeReader(rootFS, gzw) 657 658 if err := chrootarchive.Untar(rootFSReader, tmpRootFSDir, nil); err != nil { 659 return err 660 } 661 if err := rootFS.Close(); err != nil { 662 return err 663 } 664 665 if configJSON == nil { 666 return errors.New("config not found") 667 } 668 669 if err := gzw.Close(); err != nil { 670 return errors.Wrap(err, "error closing gzip writer") 671 } 672 673 var config types.PluginConfig 674 if err := json.Unmarshal(configJSON, &config); err != nil { 675 return errors.Wrap(err, "failed to parse config") 676 } 677 678 if err := pm.validateConfig(config); err != nil { 679 return err 680 } 681 682 pm.mu.Lock() 683 defer pm.mu.Unlock() 684 685 if err := rootFSBlob.Commit(ctx, 0, ""); err != nil { 686 return err 687 } 688 defer func() { 689 if err != nil { 690 go pm.GC() 691 } 692 }() 693 694 config.Rootfs = &types.PluginConfigRootfs{ 695 Type: "layers", 696 DiffIds: []string{rootFSBlob.Digest().String()}, 697 } 698 699 config.DockerVersion = dockerversion.Version 700 701 configBlob, err := pm.blobStore.Writer(ctx, content.WithRef(name+"-config.json")) 702 if err != nil { 703 return err 704 } 705 defer configBlob.Close() 706 if err := json.NewEncoder(configBlob).Encode(config); err != nil { 707 return errors.Wrap(err, "error encoding json config") 708 } 709 if err := configBlob.Commit(ctx, 0, ""); err != nil { 710 return err 711 } 712 713 configDigest := configBlob.Digest() 714 layers := []digest.Digest{rootFSBlob.Digest()} 715 716 manifest, err := buildManifest(ctx, pm.blobStore, configDigest, layers) 717 if err != nil { 718 return err 719 } 720 desc, err := writeManifest(ctx, pm.blobStore, &manifest) 721 if err != nil { 722 return 723 } 724 725 p, err := pm.createPlugin(name, configDigest, desc.Digest, layers, tmpRootFSDir, nil) 726 if err != nil { 727 return err 728 } 729 p.PluginObj.PluginReference = name 730 731 pm.publisher.Publish(EventCreate{Plugin: p.PluginObj}) 732 pm.config.LogPluginEvent(p.PluginObj.ID, name, events.ActionCreate) 733 734 return nil 735 } 736 737 func (pm *Manager) validateConfig(config types.PluginConfig) error { 738 return nil // TODO: 739 } 740 741 func splitConfigRootFSFromTar(in io.ReadCloser, config *[]byte) io.ReadCloser { 742 pr, pw := io.Pipe() 743 go func() { 744 tarReader := tar.NewReader(in) 745 tarWriter := tar.NewWriter(pw) 746 defer in.Close() 747 748 hasRootFS := false 749 750 for { 751 hdr, err := tarReader.Next() 752 if err == io.EOF { 753 if !hasRootFS { 754 pw.CloseWithError(errors.Wrap(err, "no rootfs found")) 755 return 756 } 757 // Signals end of archive. 758 tarWriter.Close() 759 pw.Close() 760 return 761 } 762 if err != nil { 763 pw.CloseWithError(errors.Wrap(err, "failed to read from tar")) 764 return 765 } 766 767 content := io.Reader(tarReader) 768 name := path.Clean(hdr.Name) 769 if path.IsAbs(name) { 770 name = name[1:] 771 } 772 if name == configFileName { 773 dt, err := io.ReadAll(content) 774 if err != nil { 775 pw.CloseWithError(errors.Wrapf(err, "failed to read %s", configFileName)) 776 return 777 } 778 *config = dt 779 } 780 if parts := strings.Split(name, "/"); len(parts) != 0 && parts[0] == rootFSFileName { 781 hdr.Name = path.Clean(path.Join(parts[1:]...)) 782 if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(strings.ToLower(hdr.Linkname), rootFSFileName+"/") { 783 hdr.Linkname = hdr.Linkname[len(rootFSFileName)+1:] 784 } 785 if err := tarWriter.WriteHeader(hdr); err != nil { 786 pw.CloseWithError(errors.Wrap(err, "error writing tar header")) 787 return 788 } 789 if _, err := pools.Copy(tarWriter, content); err != nil { 790 pw.CloseWithError(errors.Wrap(err, "error copying tar data")) 791 return 792 } 793 hasRootFS = true 794 } else { 795 io.Copy(io.Discard, content) 796 } 797 } 798 }() 799 return pr 800 } 801 802 func atomicRemoveAll(dir string) error { 803 renamed := dir + "-removing" 804 805 err := os.Rename(dir, renamed) 806 switch { 807 case os.IsNotExist(err), err == nil: 808 // even if `dir` doesn't exist, we can still try and remove `renamed` 809 case os.IsExist(err): 810 // Some previous remove failed, check if the origin dir exists 811 if e := containerfs.EnsureRemoveAll(renamed); e != nil { 812 return errors.Wrap(err, "rename target already exists and could not be removed") 813 } 814 if _, err := os.Stat(dir); os.IsNotExist(err) { 815 // origin doesn't exist, nothing left to do 816 return nil 817 } 818 819 // attempt to rename again 820 if err := os.Rename(dir, renamed); err != nil { 821 return errors.Wrap(err, "failed to rename dir for atomic removal") 822 } 823 default: 824 return errors.Wrap(err, "failed to rename dir for atomic removal") 825 } 826 827 if err := containerfs.EnsureRemoveAll(renamed); err != nil { 828 os.Rename(renamed, dir) 829 return err 830 } 831 return nil 832 }