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

     1  package images // import "github.com/docker/docker/daemon/images"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"runtime"
     8  
     9  	"github.com/containerd/containerd/platforms"
    10  	"github.com/containerd/log"
    11  	"github.com/distribution/reference"
    12  	"github.com/docker/docker/api/types/backend"
    13  	"github.com/docker/docker/api/types/registry"
    14  	"github.com/docker/docker/builder"
    15  	"github.com/docker/docker/errdefs"
    16  	"github.com/docker/docker/image"
    17  	"github.com/docker/docker/layer"
    18  	"github.com/docker/docker/pkg/progress"
    19  	"github.com/docker/docker/pkg/streamformatter"
    20  	"github.com/docker/docker/pkg/stringid"
    21  	registrypkg "github.com/docker/docker/registry"
    22  	"github.com/opencontainers/go-digest"
    23  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    24  	"github.com/pkg/errors"
    25  )
    26  
    27  type roLayer struct {
    28  	released   bool
    29  	layerStore layer.Store
    30  	roLayer    layer.Layer
    31  }
    32  
    33  func (l *roLayer) ContentStoreDigest() digest.Digest {
    34  	return ""
    35  }
    36  
    37  func (l *roLayer) DiffID() layer.DiffID {
    38  	if l.roLayer == nil {
    39  		return layer.DigestSHA256EmptyTar
    40  	}
    41  	return l.roLayer.DiffID()
    42  }
    43  
    44  func (l *roLayer) Release() error {
    45  	if l.released {
    46  		return nil
    47  	}
    48  	if l.roLayer != nil {
    49  		metadata, err := l.layerStore.Release(l.roLayer)
    50  		layer.LogReleaseMetadata(metadata)
    51  		if err != nil {
    52  			return errors.Wrap(err, "failed to release ROLayer")
    53  		}
    54  	}
    55  	l.roLayer = nil
    56  	l.released = true
    57  	return nil
    58  }
    59  
    60  func (l *roLayer) NewRWLayer() (builder.RWLayer, error) {
    61  	var chainID layer.ChainID
    62  	if l.roLayer != nil {
    63  		chainID = l.roLayer.ChainID()
    64  	}
    65  
    66  	mountID := stringid.GenerateRandomID()
    67  	newLayer, err := l.layerStore.CreateRWLayer(mountID, chainID, nil)
    68  	if err != nil {
    69  		return nil, errors.Wrap(err, "failed to create rwlayer")
    70  	}
    71  
    72  	rwLayer := &rwLayer{layerStore: l.layerStore, rwLayer: newLayer}
    73  
    74  	fs, err := newLayer.Mount("")
    75  	if err != nil {
    76  		rwLayer.Release()
    77  		return nil, err
    78  	}
    79  
    80  	rwLayer.fs = fs
    81  
    82  	return rwLayer, nil
    83  }
    84  
    85  type rwLayer struct {
    86  	released   bool
    87  	layerStore layer.Store
    88  	rwLayer    layer.RWLayer
    89  	fs         string
    90  }
    91  
    92  func (l *rwLayer) Root() string {
    93  	return l.fs
    94  }
    95  
    96  func (l *rwLayer) Commit() (builder.ROLayer, error) {
    97  	stream, err := l.rwLayer.TarStream()
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	defer stream.Close()
   102  
   103  	var chainID layer.ChainID
   104  	if parent := l.rwLayer.Parent(); parent != nil {
   105  		chainID = parent.ChainID()
   106  	}
   107  
   108  	newLayer, err := l.layerStore.Register(stream, chainID)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	// TODO: An optimization would be to handle empty layers before returning
   113  	return &roLayer{layerStore: l.layerStore, roLayer: newLayer}, nil
   114  }
   115  
   116  func (l *rwLayer) Release() error {
   117  	if l.released {
   118  		return nil
   119  	}
   120  
   121  	if l.fs != "" {
   122  		if err := l.rwLayer.Unmount(); err != nil {
   123  			return errors.Wrap(err, "failed to unmount RWLayer")
   124  		}
   125  		l.fs = ""
   126  	}
   127  
   128  	metadata, err := l.layerStore.ReleaseRWLayer(l.rwLayer)
   129  	layer.LogReleaseMetadata(metadata)
   130  	if err != nil {
   131  		return errors.Wrap(err, "failed to release RWLayer")
   132  	}
   133  	l.released = true
   134  	return nil
   135  }
   136  
   137  func newROLayerForImage(img *image.Image, layerStore layer.Store) (builder.ROLayer, error) {
   138  	if img == nil || img.RootFS.ChainID() == "" {
   139  		return &roLayer{layerStore: layerStore}, nil
   140  	}
   141  	// Hold a reference to the image layer so that it can't be removed before
   142  	// it is released
   143  	lyr, err := layerStore.Get(img.RootFS.ChainID())
   144  	if err != nil {
   145  		return nil, errors.Wrapf(err, "failed to get layer for image %s", img.ImageID())
   146  	}
   147  	return &roLayer{layerStore: layerStore, roLayer: lyr}, nil
   148  }
   149  
   150  // TODO: could this use the regular daemon PullImage ?
   151  func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConfigs map[string]registry.AuthConfig, output io.Writer, platform *ocispec.Platform) (*image.Image, error) {
   152  	ref, err := reference.ParseNormalizedNamed(name)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	ref = reference.TagNameOnly(ref)
   157  
   158  	pullRegistryAuth := &registry.AuthConfig{}
   159  	if len(authConfigs) > 0 {
   160  		// The request came with a full auth config, use it
   161  		repoInfo, err := i.registryService.ResolveRepository(ref)
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  
   166  		resolvedConfig := registrypkg.ResolveAuthConfig(authConfigs, repoInfo.Index)
   167  		pullRegistryAuth = &resolvedConfig
   168  	}
   169  
   170  	if err := i.pullImageWithReference(ctx, ref, platform, nil, pullRegistryAuth, output); err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	img, err := i.GetImage(ctx, name, backend.GetImageOpts{Platform: platform})
   175  	if errdefs.IsNotFound(err) && img != nil && platform != nil {
   176  		imgPlat := ocispec.Platform{
   177  			OS:           img.OS,
   178  			Architecture: img.BaseImgArch(),
   179  			Variant:      img.BaseImgVariant(),
   180  		}
   181  
   182  		p := *platform
   183  		if !platforms.Only(p).Match(imgPlat) {
   184  			po := streamformatter.NewJSONProgressOutput(output, false)
   185  			progress.Messagef(po, "", `
   186  WARNING: Pulled image with specified platform (%s), but the resulting image's configured platform (%s) does not match.
   187  This is most likely caused by a bug in the build system that created the fetched image (%s).
   188  Please notify the image author to correct the configuration.`,
   189  				platforms.Format(p), platforms.Format(imgPlat), name,
   190  			)
   191  			log.G(ctx).WithError(err).WithField("image", name).Warn("Ignoring error about platform mismatch where the manifest list points to an image whose configuration does not match the platform in the manifest.")
   192  			err = nil
   193  		}
   194  	}
   195  	return img, err
   196  }
   197  
   198  // GetImageAndReleasableLayer returns an image and releaseable layer for a reference or ID.
   199  // Every call to GetImageAndReleasableLayer MUST call releasableLayer.Release() to prevent
   200  // leaking of layers.
   201  func (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error) {
   202  	if refOrID == "" { // FROM scratch
   203  		if runtime.GOOS == "windows" {
   204  			return nil, nil, fmt.Errorf(`"FROM scratch" is not supported on Windows`)
   205  		}
   206  		if opts.Platform != nil {
   207  			if err := image.CheckOS(opts.Platform.OS); err != nil {
   208  				return nil, nil, err
   209  			}
   210  		}
   211  		lyr, err := newROLayerForImage(nil, i.layerStore)
   212  		return nil, lyr, err
   213  	}
   214  
   215  	if opts.PullOption != backend.PullOptionForcePull {
   216  		img, err := i.GetImage(ctx, refOrID, backend.GetImageOpts{Platform: opts.Platform})
   217  		if err != nil && opts.PullOption == backend.PullOptionNoPull {
   218  			return nil, nil, err
   219  		}
   220  		if err != nil && !errdefs.IsNotFound(err) {
   221  			return nil, nil, err
   222  		}
   223  		if img != nil {
   224  			if err := image.CheckOS(img.OperatingSystem()); err != nil {
   225  				return nil, nil, err
   226  			}
   227  			lyr, err := newROLayerForImage(img, i.layerStore)
   228  			return img, lyr, err
   229  		}
   230  	}
   231  
   232  	img, err := i.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform)
   233  	if err != nil {
   234  		return nil, nil, err
   235  	}
   236  	if err := image.CheckOS(img.OperatingSystem()); err != nil {
   237  		return nil, nil, err
   238  	}
   239  	lyr, err := newROLayerForImage(img, i.layerStore)
   240  	return img, lyr, err
   241  }
   242  
   243  // CreateImage creates a new image by adding a config and ID to the image store.
   244  // This is similar to LoadImage() except that it receives JSON encoded bytes of
   245  // an image instead of a tar archive.
   246  func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent string, _ digest.Digest) (builder.Image, error) {
   247  	id, err := i.imageStore.Create(config)
   248  	if err != nil {
   249  		return nil, errors.Wrapf(err, "failed to create image")
   250  	}
   251  
   252  	if parent != "" {
   253  		if err := i.imageStore.SetParent(id, image.ID(parent)); err != nil {
   254  			return nil, errors.Wrapf(err, "failed to set parent %s", parent)
   255  		}
   256  	}
   257  	if err := i.imageStore.SetBuiltLocally(id); err != nil {
   258  		return nil, errors.Wrapf(err, "failed to mark image %s as built locally", id)
   259  	}
   260  
   261  	return i.imageStore.Get(id)
   262  }