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