github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/daemon/containerd/image_builder.go (about) 1 package containerd 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "runtime" 10 "time" 11 12 "github.com/containerd/containerd" 13 "github.com/containerd/containerd/content" 14 cerrdefs "github.com/containerd/containerd/errdefs" 15 "github.com/containerd/containerd/leases" 16 "github.com/containerd/containerd/mount" 17 "github.com/containerd/containerd/platforms" 18 "github.com/containerd/containerd/rootfs" 19 "github.com/distribution/reference" 20 "github.com/Prakhar-Agarwal-byte/moby/api/types/backend" 21 imagetypes "github.com/Prakhar-Agarwal-byte/moby/api/types/image" 22 "github.com/Prakhar-Agarwal-byte/moby/api/types/registry" 23 "github.com/Prakhar-Agarwal-byte/moby/internal/compatcontext" 24 registrypkg "github.com/Prakhar-Agarwal-byte/moby/registry" 25 26 // "github.com/Prakhar-Agarwal-byte/moby/api/types/container" 27 containerdimages "github.com/containerd/containerd/images" 28 "github.com/containerd/log" 29 "github.com/Prakhar-Agarwal-byte/moby/api/types/image" 30 "github.com/Prakhar-Agarwal-byte/moby/builder" 31 "github.com/Prakhar-Agarwal-byte/moby/errdefs" 32 dimage "github.com/Prakhar-Agarwal-byte/moby/image" 33 "github.com/Prakhar-Agarwal-byte/moby/layer" 34 "github.com/Prakhar-Agarwal-byte/moby/pkg/archive" 35 "github.com/Prakhar-Agarwal-byte/moby/pkg/progress" 36 "github.com/Prakhar-Agarwal-byte/moby/pkg/streamformatter" 37 "github.com/Prakhar-Agarwal-byte/moby/pkg/stringid" 38 "github.com/opencontainers/go-digest" 39 "github.com/opencontainers/image-spec/identity" 40 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 41 ) 42 43 const imageLabelClassicBuilderParent = "org.mobyproject.image.parent" 44 45 // GetImageAndReleasableLayer returns an image and releaseable layer for a 46 // reference or ID. Every call to GetImageAndReleasableLayer MUST call 47 // releasableLayer.Release() to prevent leaking of layers. 48 func (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error) { 49 if refOrID == "" { // FROM scratch 50 if runtime.GOOS == "windows" { 51 return nil, nil, fmt.Errorf(`"FROM scratch" is not supported on Windows`) 52 } 53 if opts.Platform != nil { 54 if err := dimage.CheckOS(opts.Platform.OS); err != nil { 55 return nil, nil, err 56 } 57 } 58 return nil, &rolayer{ 59 c: i.client, 60 snapshotter: i.snapshotter, 61 }, nil 62 } 63 64 if opts.PullOption != backend.PullOptionForcePull { 65 // TODO(laurazard): same as below 66 img, err := i.GetImage(ctx, refOrID, image.GetImageOpts{Platform: opts.Platform}) 67 if err != nil && opts.PullOption == backend.PullOptionNoPull { 68 return nil, nil, err 69 } 70 imgDesc, err := i.resolveDescriptor(ctx, refOrID) 71 if err != nil && !errdefs.IsNotFound(err) { 72 return nil, nil, err 73 } 74 if img != nil { 75 if err := dimage.CheckOS(img.OperatingSystem()); err != nil { 76 return nil, nil, err 77 } 78 79 roLayer, err := newROLayerForImage(ctx, &imgDesc, i, opts.Platform) 80 if err != nil { 81 return nil, nil, err 82 } 83 84 return img, roLayer, nil 85 } 86 } 87 88 ctx, _, err := i.client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour)) 89 if err != nil { 90 return nil, nil, fmt.Errorf("failed to create lease for commit: %w", err) 91 } 92 93 // TODO(laurazard): do we really need a new method here to pull the image? 94 imgDesc, err := i.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform) 95 if err != nil { 96 return nil, nil, err 97 } 98 99 // TODO(laurazard): pullForBuilder should return whatever we 100 // need here instead of having to go and get it again 101 img, err := i.GetImage(ctx, refOrID, imagetypes.GetImageOpts{ 102 Platform: opts.Platform, 103 }) 104 if err != nil { 105 return nil, nil, err 106 } 107 108 roLayer, err := newROLayerForImage(ctx, imgDesc, i, opts.Platform) 109 if err != nil { 110 return nil, nil, err 111 } 112 113 return img, roLayer, nil 114 } 115 116 func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConfigs map[string]registry.AuthConfig, output io.Writer, platform *ocispec.Platform) (*ocispec.Descriptor, error) { 117 ref, err := reference.ParseNormalizedNamed(name) 118 if err != nil { 119 return nil, err 120 } 121 122 pullRegistryAuth := ®istry.AuthConfig{} 123 if len(authConfigs) > 0 { 124 // The request came with a full auth config, use it 125 repoInfo, err := i.registryService.ResolveRepository(ref) 126 if err != nil { 127 return nil, err 128 } 129 130 resolvedConfig := registrypkg.ResolveAuthConfig(authConfigs, repoInfo.Index) 131 pullRegistryAuth = &resolvedConfig 132 } 133 134 if err := i.PullImage(ctx, reference.TagNameOnly(ref), platform, nil, pullRegistryAuth, output); err != nil { 135 return nil, err 136 } 137 138 img, err := i.GetImage(ctx, name, imagetypes.GetImageOpts{Platform: platform}) 139 if err != nil { 140 if errdefs.IsNotFound(err) && img != nil && platform != nil { 141 imgPlat := ocispec.Platform{ 142 OS: img.OS, 143 Architecture: img.BaseImgArch(), 144 Variant: img.BaseImgVariant(), 145 } 146 147 p := *platform 148 if !platforms.Only(p).Match(imgPlat) { 149 po := streamformatter.NewJSONProgressOutput(output, false) 150 progress.Messagef(po, "", ` 151 WARNING: Pulled image with specified platform (%s), but the resulting image's configured platform (%s) does not match. 152 This is most likely caused by a bug in the build system that created the fetched image (%s). 153 Please notify the image author to correct the configuration.`, 154 platforms.Format(p), platforms.Format(imgPlat), name, 155 ) 156 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.") 157 } 158 } else { 159 return nil, err 160 } 161 } 162 163 if err := dimage.CheckOS(img.OperatingSystem()); err != nil { 164 return nil, err 165 } 166 167 imgDesc, err := i.resolveDescriptor(ctx, name) 168 if err != nil { 169 return nil, err 170 } 171 172 return &imgDesc, err 173 } 174 175 func newROLayerForImage(ctx context.Context, imgDesc *ocispec.Descriptor, i *ImageService, platform *ocispec.Platform) (builder.ROLayer, error) { 176 if imgDesc == nil { 177 return nil, fmt.Errorf("can't make an RO layer for a nil image :'(") 178 } 179 180 platMatcher := platforms.Default() 181 if platform != nil { 182 platMatcher = platforms.Only(*platform) 183 } 184 185 confDesc, err := containerdimages.Config(ctx, i.client.ContentStore(), *imgDesc, platMatcher) 186 if err != nil { 187 return nil, err 188 } 189 190 diffIDs, err := containerdimages.RootFS(ctx, i.client.ContentStore(), confDesc) 191 if err != nil { 192 return nil, err 193 } 194 195 // TODO(vvoland): Check if image is unpacked, and unpack it if it's not. 196 imageSnapshotID := identity.ChainID(diffIDs).String() 197 198 snapshotter := i.StorageDriver() 199 _, lease, err := createLease(ctx, i.client.LeasesService()) 200 if err != nil { 201 return nil, errdefs.System(fmt.Errorf("failed to lease image snapshot %s: %w", imageSnapshotID, err)) 202 } 203 204 return &rolayer{ 205 key: imageSnapshotID, 206 c: i.client, 207 snapshotter: snapshotter, 208 diffID: "", // Image RO layer doesn't have a diff. 209 contentStoreDigest: "", 210 lease: &lease, 211 }, nil 212 } 213 214 func createLease(ctx context.Context, lm leases.Manager) (context.Context, leases.Lease, error) { 215 lease, err := lm.Create(ctx, 216 leases.WithExpiration(time.Hour*24), 217 leases.WithLabels(map[string]string{ 218 "org.mobyproject.lease.classicbuilder": "true", 219 }), 220 ) 221 if err != nil { 222 return nil, leases.Lease{}, fmt.Errorf("failed to create a lease for snapshot: %w", err) 223 } 224 225 return leases.WithLease(ctx, lease.ID), lease, nil 226 } 227 228 type rolayer struct { 229 key string 230 c *containerd.Client 231 snapshotter string 232 diffID layer.DiffID 233 contentStoreDigest digest.Digest 234 lease *leases.Lease 235 } 236 237 func (rl *rolayer) ContentStoreDigest() digest.Digest { 238 return rl.contentStoreDigest 239 } 240 241 func (rl *rolayer) DiffID() layer.DiffID { 242 if rl.diffID == "" { 243 return layer.DigestSHA256EmptyTar 244 } 245 return rl.diffID 246 } 247 248 func (rl *rolayer) Release() error { 249 if rl.lease != nil { 250 lm := rl.c.LeasesService() 251 err := lm.Delete(context.TODO(), *rl.lease) 252 if err != nil { 253 return err 254 } 255 rl.lease = nil 256 } 257 return nil 258 } 259 260 // NewRWLayer creates a new read-write layer for the builder 261 func (rl *rolayer) NewRWLayer() (_ builder.RWLayer, outErr error) { 262 snapshotter := rl.c.SnapshotService(rl.snapshotter) 263 264 key := stringid.GenerateRandomID() 265 266 ctx, lease, err := createLease(context.TODO(), rl.c.LeasesService()) 267 if err != nil { 268 return nil, err 269 } 270 defer func() { 271 if outErr != nil { 272 if err := rl.c.LeasesService().Delete(ctx, lease); err != nil { 273 log.G(ctx).WithError(err).Warn("failed to remove lease after NewRWLayer error") 274 } 275 } 276 }() 277 278 mounts, err := snapshotter.Prepare(ctx, key, rl.key) 279 if err != nil { 280 return nil, err 281 } 282 283 root, err := os.MkdirTemp(os.TempDir(), "rootfs-mount") 284 if err != nil { 285 return nil, err 286 } 287 if err := mount.All(mounts, root); err != nil { 288 return nil, err 289 } 290 291 return &rwlayer{ 292 key: key, 293 parent: rl.key, 294 c: rl.c, 295 snapshotter: rl.snapshotter, 296 root: root, 297 lease: &lease, 298 }, nil 299 } 300 301 type rwlayer struct { 302 key string 303 parent string 304 c *containerd.Client 305 snapshotter string 306 root string 307 lease *leases.Lease 308 } 309 310 func (rw *rwlayer) Root() string { 311 return rw.root 312 } 313 314 func (rw *rwlayer) Commit() (_ builder.ROLayer, outErr error) { 315 snapshotter := rw.c.SnapshotService(rw.snapshotter) 316 317 key := stringid.GenerateRandomID() 318 319 lm := rw.c.LeasesService() 320 ctx, lease, err := createLease(context.TODO(), lm) 321 if err != nil { 322 return nil, err 323 } 324 defer func() { 325 if outErr != nil { 326 if err := lm.Delete(ctx, lease); err != nil { 327 log.G(ctx).WithError(err).Warn("failed to remove lease after NewRWLayer error") 328 } 329 } 330 }() 331 332 err = snapshotter.Commit(ctx, key, rw.key) 333 if err != nil && !cerrdefs.IsAlreadyExists(err) { 334 return nil, err 335 } 336 337 differ := rw.c.DiffService() 338 desc, err := rootfs.CreateDiff(ctx, key, snapshotter, differ) 339 if err != nil { 340 return nil, err 341 } 342 info, err := rw.c.ContentStore().Info(ctx, desc.Digest) 343 if err != nil { 344 return nil, err 345 } 346 diffIDStr, ok := info.Labels["containerd.io/uncompressed"] 347 if !ok { 348 return nil, fmt.Errorf("invalid differ response with no diffID") 349 } 350 diffID, err := digest.Parse(diffIDStr) 351 if err != nil { 352 return nil, err 353 } 354 355 return &rolayer{ 356 key: key, 357 c: rw.c, 358 snapshotter: rw.snapshotter, 359 diffID: layer.DiffID(diffID), 360 contentStoreDigest: desc.Digest, 361 lease: &lease, 362 }, nil 363 } 364 365 func (rw *rwlayer) Release() (outErr error) { 366 if rw.root == "" { // nothing to release 367 return nil 368 } 369 370 if err := mount.UnmountAll(rw.root, 0); err != nil && !errors.Is(err, os.ErrNotExist) { 371 log.G(context.TODO()).WithError(err).WithField("root", rw.root).Error("failed to unmount ROLayer") 372 return err 373 } 374 if err := os.Remove(rw.root); err != nil && !errors.Is(err, os.ErrNotExist) { 375 log.G(context.TODO()).WithError(err).WithField("dir", rw.root).Error("failed to remove mount temp dir") 376 return err 377 } 378 rw.root = "" 379 380 if rw.lease != nil { 381 lm := rw.c.LeasesService() 382 err := lm.Delete(context.TODO(), *rw.lease) 383 if err != nil { 384 log.G(context.TODO()).WithError(err).Warn("failed to delete lease when releasing RWLayer") 385 } else { 386 rw.lease = nil 387 } 388 } 389 390 return nil 391 } 392 393 // CreateImage creates a new image by adding a config and ID to the image store. 394 // This is similar to LoadImage() except that it receives JSON encoded bytes of 395 // an image instead of a tar archive. 396 func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent string, layerDigest digest.Digest) (builder.Image, error) { 397 imgToCreate, err := dimage.NewFromJSON(config) 398 if err != nil { 399 return nil, err 400 } 401 402 ociImgToCreate := dockerImageToDockerOCIImage(*imgToCreate) 403 404 var layers []ocispec.Descriptor 405 406 var parentDigest digest.Digest 407 // if the image has a parent, we need to start with the parents layers descriptors 408 if parent != "" { 409 parentDesc, err := i.resolveDescriptor(ctx, parent) 410 if err != nil { 411 return nil, err 412 } 413 parentImageManifest, err := containerdimages.Manifest(ctx, i.client.ContentStore(), parentDesc, platforms.Default()) 414 if err != nil { 415 return nil, err 416 } 417 418 layers = parentImageManifest.Layers 419 parentDigest = parentDesc.Digest 420 } 421 422 cs := i.client.ContentStore() 423 424 ra, err := cs.ReaderAt(ctx, ocispec.Descriptor{Digest: layerDigest}) 425 if err != nil { 426 return nil, fmt.Errorf("failed to read diff archive: %w", err) 427 } 428 defer ra.Close() 429 430 empty, err := archive.IsEmpty(content.NewReader(ra)) 431 if err != nil { 432 return nil, fmt.Errorf("failed to check if archive is empty: %w", err) 433 } 434 if !empty { 435 info, err := cs.Info(ctx, layerDigest) 436 if err != nil { 437 return nil, err 438 } 439 440 layers = append(layers, ocispec.Descriptor{ 441 MediaType: containerdimages.MediaTypeDockerSchema2LayerGzip, 442 Digest: layerDigest, 443 Size: info.Size, 444 }) 445 } 446 447 // necessary to prevent the contents from being GC'd 448 // between writing them here and creating an image 449 ctx, release, err := i.client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour)) 450 if err != nil { 451 return nil, err 452 } 453 defer func() { 454 if err := release(compatcontext.WithoutCancel(ctx)); err != nil { 455 log.G(ctx).WithError(err).Warn("failed to release lease created for create") 456 } 457 }() 458 459 commitManifestDesc, err := writeContentsForImage(ctx, i.snapshotter, i.client.ContentStore(), ociImgToCreate, layers) 460 if err != nil { 461 return nil, err 462 } 463 464 // image create 465 img := containerdimages.Image{ 466 Name: danglingImageName(commitManifestDesc.Digest), 467 Target: commitManifestDesc, 468 CreatedAt: time.Now(), 469 Labels: map[string]string{ 470 imageLabelClassicBuilderParent: parentDigest.String(), 471 }, 472 } 473 474 createdImage, err := i.client.ImageService().Update(ctx, img) 475 if err != nil { 476 if !cerrdefs.IsNotFound(err) { 477 return nil, err 478 } 479 480 if createdImage, err = i.client.ImageService().Create(ctx, img); err != nil { 481 return nil, fmt.Errorf("failed to create new image: %w", err) 482 } 483 } 484 485 if err := i.unpackImage(ctx, i.StorageDriver(), img, commitManifestDesc); err != nil { 486 return nil, err 487 } 488 489 newImage := dimage.Clone(imgToCreate, dimage.ID(createdImage.Target.Digest)) 490 return newImage, nil 491 }