github.com/moby/docker@v26.1.3+incompatible/daemon/images/store.go (about)

     1  package images
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  
     7  	"github.com/containerd/containerd/content"
     8  	cerrdefs "github.com/containerd/containerd/errdefs"
     9  	"github.com/containerd/containerd/leases"
    10  	"github.com/containerd/containerd/namespaces"
    11  	"github.com/containerd/log"
    12  	"github.com/docker/docker/distribution"
    13  	"github.com/docker/docker/image"
    14  	"github.com/docker/docker/layer"
    15  	"github.com/opencontainers/go-digest"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  const imageKeyPrefix = "moby-image-"
    20  
    21  func imageKey(dgst string) string {
    22  	return imageKeyPrefix + dgst
    23  }
    24  
    25  // imageStoreWithLease wraps the configured image store with one that deletes the lease
    26  // reigstered for a given image ID, if one exists
    27  //
    28  // This is used by the main image service to wrap delete calls to the real image store.
    29  type imageStoreWithLease struct {
    30  	image.Store
    31  	leases leases.Manager
    32  
    33  	// Normally we'd pass namespace down through a context.Context, however...
    34  	// The interface for image store doesn't allow this, so we store it here.
    35  	ns string
    36  }
    37  
    38  func (s *imageStoreWithLease) Delete(id image.ID) ([]layer.Metadata, error) {
    39  	ctx := namespaces.WithNamespace(context.TODO(), s.ns)
    40  	if err := s.leases.Delete(ctx, leases.Lease{ID: imageKey(id.String())}); err != nil && !cerrdefs.IsNotFound(err) {
    41  		return nil, errors.Wrap(err, "error deleting lease")
    42  	}
    43  	return s.Store.Delete(id)
    44  }
    45  
    46  // iamgeStoreForPull is created for each pull It wraps an underlying image store
    47  // to handle registering leases for content fetched in a single image pull.
    48  type imageStoreForPull struct {
    49  	distribution.ImageConfigStore
    50  	leases   leases.Manager
    51  	ingested *contentStoreForPull
    52  }
    53  
    54  func (s *imageStoreForPull) Put(ctx context.Context, config []byte) (digest.Digest, error) {
    55  	id, err := s.ImageConfigStore.Put(ctx, config)
    56  	if err != nil {
    57  		return "", err
    58  	}
    59  	return id, s.updateLease(ctx, id)
    60  }
    61  
    62  func (s *imageStoreForPull) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
    63  	id, err := s.ImageConfigStore.Get(ctx, dgst)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	return id, s.updateLease(ctx, dgst)
    68  }
    69  
    70  func (s *imageStoreForPull) updateLease(ctx context.Context, dgst digest.Digest) error {
    71  	leaseID := imageKey(dgst.String())
    72  	lease, err := s.leases.Create(ctx, leases.WithID(leaseID))
    73  	if err != nil {
    74  		if !cerrdefs.IsAlreadyExists(err) {
    75  			return errors.Wrap(err, "error creating lease")
    76  		}
    77  		lease = leases.Lease{ID: leaseID}
    78  	}
    79  
    80  	digested := s.ingested.getDigested()
    81  	resource := leases.Resource{
    82  		Type: "content",
    83  	}
    84  	for _, dgst := range digested {
    85  		log.G(ctx).WithFields(log.Fields{
    86  			"digest": dgst,
    87  			"lease":  lease.ID,
    88  		}).Debug("Adding content digest to lease")
    89  
    90  		resource.ID = dgst.String()
    91  		if err := s.leases.AddResource(ctx, lease, resource); err != nil {
    92  			return errors.Wrapf(err, "error adding content digest to lease: %s", dgst)
    93  		}
    94  	}
    95  	return nil
    96  }
    97  
    98  // contentStoreForPull is used to wrap the configured content store to
    99  // add lease management for a single `pull`
   100  // It stores all committed digests so that `imageStoreForPull` can add
   101  // the digsted resources to the lease for an image.
   102  type contentStoreForPull struct {
   103  	distribution.ContentStore
   104  	leases leases.Manager
   105  
   106  	mu       sync.Mutex
   107  	digested []digest.Digest
   108  }
   109  
   110  func (c *contentStoreForPull) addDigested(dgst digest.Digest) {
   111  	c.mu.Lock()
   112  	c.digested = append(c.digested, dgst)
   113  	c.mu.Unlock()
   114  }
   115  
   116  func (c *contentStoreForPull) getDigested() []digest.Digest {
   117  	c.mu.Lock()
   118  	digested := make([]digest.Digest, len(c.digested))
   119  	copy(digested, c.digested)
   120  	c.mu.Unlock()
   121  	return digested
   122  }
   123  
   124  func (c *contentStoreForPull) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
   125  	w, err := c.ContentStore.Writer(ctx, opts...)
   126  	if err != nil {
   127  		if cerrdefs.IsAlreadyExists(err) {
   128  			var cfg content.WriterOpts
   129  			for _, o := range opts {
   130  				if err := o(&cfg); err != nil {
   131  					return nil, err
   132  				}
   133  			}
   134  			c.addDigested(cfg.Desc.Digest)
   135  		}
   136  		return nil, err
   137  	}
   138  	return &contentWriter{
   139  		cs:     c,
   140  		Writer: w,
   141  	}, nil
   142  }
   143  
   144  type contentWriter struct {
   145  	cs *contentStoreForPull
   146  	content.Writer
   147  }
   148  
   149  func (w *contentWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
   150  	err := w.Writer.Commit(ctx, size, expected, opts...)
   151  	if err == nil || cerrdefs.IsAlreadyExists(err) {
   152  		w.cs.addDigested(expected)
   153  	}
   154  	return err
   155  }