moby/image/image.go
Paweł Gronowski 01214bafd2
c8d/builder: Don't drop fields from created image
Previous image created a new partially filled image.
This caused child images to lose their parent's layers.

Instead of creating a new object and trying to replace its fields, just
clone the original passed image and change its ID to the manifest
digest.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2023-08-24 09:44:12 +02:00

308 lines
8.5 KiB
Go

package image // import "github.com/docker/docker/image"
import (
"encoding/json"
"errors"
"io"
"runtime"
"strings"
"time"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/layer"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ID is the content-addressable ID of an image.
type ID digest.Digest
func (id ID) String() string {
return id.Digest().String()
}
// Digest converts ID into a digest
func (id ID) Digest() digest.Digest {
return digest.Digest(id)
}
// IDFromDigest creates an ID from a digest
//
// Deprecated: cast to an ID using ID(digest).
func IDFromDigest(digest digest.Digest) ID {
return ID(digest)
}
// V1Image stores the V1 image configuration.
type V1Image struct {
// ID is a unique 64 character identifier of the image
ID string `json:"id,omitempty"`
// Parent is the ID of the parent image.
//
// Depending on how the image was created, this field may be empty and
// is only set for images that were built/created locally. This field
// is empty if the image was pulled from an image registry.
Parent string `json:"parent,omitempty"`
// Comment is an optional message that can be set when committing or
// importing the image.
Comment string `json:"comment,omitempty"`
// Created is the timestamp at which the image was created
Created *time.Time `json:"created"`
// Container is the ID of the container that was used to create the image.
//
// Depending on how the image was created, this field may be empty.
Container string `json:"container,omitempty"`
// ContainerConfig is the configuration of the container that was committed
// into the image.
ContainerConfig container.Config `json:"container_config,omitempty"`
// DockerVersion is the version of Docker that was used to build the image.
//
// Depending on how the image was created, this field may be empty.
DockerVersion string `json:"docker_version,omitempty"`
// Author is the name of the author that was specified when committing the
// image, or as specified through MAINTAINER (deprecated) in the Dockerfile.
Author string `json:"author,omitempty"`
// Config is the configuration of the container received from the client.
Config *container.Config `json:"config,omitempty"`
// Architecture is the hardware CPU architecture that the image runs on.
Architecture string `json:"architecture,omitempty"`
// Variant is the CPU architecture variant (presently ARM-only).
Variant string `json:"variant,omitempty"`
// OS is the Operating System the image is built to run on.
OS string `json:"os,omitempty"`
// Size is the total size of the image including all layers it is composed of.
Size int64 `json:",omitempty"`
}
// Image stores the image configuration
type Image struct {
V1Image
// Parent is the ID of the parent image.
//
// Depending on how the image was created, this field may be empty and
// is only set for images that were built/created locally. This field
// is empty if the image was pulled from an image registry.
Parent ID `json:"parent,omitempty"` //nolint:govet
// RootFS contains information about the image's RootFS, including the
// layer IDs.
RootFS *RootFS `json:"rootfs,omitempty"`
History []History `json:"history,omitempty"`
// OsVersion is the version of the Operating System the image is built to
// run on (especially for Windows).
OSVersion string `json:"os.version,omitempty"`
OSFeatures []string `json:"os.features,omitempty"`
// rawJSON caches the immutable JSON associated with this image.
rawJSON []byte
// computedID is the ID computed from the hash of the image config.
// Not to be confused with the legacy V1 ID in V1Image.
computedID ID
// Details holds additional details about image
Details *Details `json:"-"`
}
// Details provides additional image data
type Details struct {
References []reference.Named
Size int64
Metadata map[string]string
Driver string
LastUpdated time.Time
}
// RawJSON returns the immutable JSON associated with the image.
func (img *Image) RawJSON() []byte {
return img.rawJSON
}
// ID returns the image's content-addressable ID.
func (img *Image) ID() ID {
return img.computedID
}
// ImageID stringifies ID.
func (img *Image) ImageID() string {
return img.ID().String()
}
// RunConfig returns the image's container config.
func (img *Image) RunConfig() *container.Config {
return img.Config
}
// BaseImgArch returns the image's architecture. If not populated, defaults to the host runtime arch.
func (img *Image) BaseImgArch() string {
arch := img.Architecture
if arch == "" {
arch = runtime.GOARCH
}
return arch
}
// BaseImgVariant returns the image's variant, whether populated or not.
// This avoids creating an inconsistency where the stored image variant
// is "greater than" (i.e. v8 vs v6) the actual image variant.
func (img *Image) BaseImgVariant() string {
return img.Variant
}
// OperatingSystem returns the image's operating system. If not populated, defaults to the host runtime OS.
func (img *Image) OperatingSystem() string {
os := img.OS
if os == "" {
os = runtime.GOOS
}
return os
}
// Platform generates an OCI platform from the image
func (img *Image) Platform() ocispec.Platform {
return ocispec.Platform{
Architecture: img.Architecture,
OS: img.OS,
OSVersion: img.OSVersion,
OSFeatures: img.OSFeatures,
Variant: img.Variant,
}
}
// MarshalJSON serializes the image to JSON. It sorts the top-level keys so
// that JSON that's been manipulated by a push/pull cycle with a legacy
// registry won't end up with a different key order.
func (img *Image) MarshalJSON() ([]byte, error) {
type MarshalImage Image
pass1, err := json.Marshal(MarshalImage(*img))
if err != nil {
return nil, err
}
var c map[string]*json.RawMessage
if err := json.Unmarshal(pass1, &c); err != nil {
return nil, err
}
return json.Marshal(c)
}
// ChildConfig is the configuration to apply to an Image to create a new
// Child image. Other properties of the image are copied from the parent.
type ChildConfig struct {
ContainerID string
Author string
Comment string
DiffID layer.DiffID
ContainerConfig *container.Config
Config *container.Config
}
// NewImage creates a new image with the given ID
func NewImage(id ID) *Image {
return &Image{
computedID: id,
}
}
// NewChildImage creates a new Image as a child of this image.
func NewChildImage(img *Image, child ChildConfig, os string) *Image {
isEmptyLayer := layer.IsEmpty(child.DiffID)
var rootFS *RootFS
if img.RootFS != nil {
rootFS = img.RootFS.Clone()
} else {
rootFS = NewRootFS()
}
if !isEmptyLayer {
rootFS.Append(child.DiffID)
}
imgHistory := NewHistory(
child.Author,
child.Comment,
strings.Join(child.ContainerConfig.Cmd, " "),
isEmptyLayer)
return &Image{
V1Image: V1Image{
DockerVersion: dockerversion.Version,
Config: child.Config,
Architecture: img.BaseImgArch(),
Variant: img.BaseImgVariant(),
OS: os,
Container: child.ContainerID,
ContainerConfig: *child.ContainerConfig,
Author: child.Author,
Created: imgHistory.Created,
},
RootFS: rootFS,
History: append(img.History, imgHistory),
OSFeatures: img.OSFeatures,
OSVersion: img.OSVersion,
}
}
// Clone clones an image and changes ID.
func Clone(base *Image, id ID) *Image {
img := *base
img.RootFS = img.RootFS.Clone()
img.V1Image.ID = id.String()
img.computedID = id
return &img
}
// History stores build commands that were used to create an image
type History = ocispec.History
// NewHistory creates a new history struct from arguments, and sets the created
// time to the current time in UTC
func NewHistory(author, comment, createdBy string, isEmptyLayer bool) History {
now := time.Now().UTC()
return History{
Author: author,
Created: &now,
CreatedBy: createdBy,
Comment: comment,
EmptyLayer: isEmptyLayer,
}
}
// Exporter provides interface for loading and saving images
type Exporter interface {
Load(io.ReadCloser, io.Writer, bool) error
// TODO: Load(net.Context, io.ReadCloser, <- chan StatusMessage) error
Save([]string, io.Writer) error
}
// NewFromJSON creates an Image configuration from json.
func NewFromJSON(src []byte) (*Image, error) {
img := &Image{}
if err := json.Unmarshal(src, img); err != nil {
return nil, err
}
if img.RootFS == nil {
return nil, errors.New("invalid image JSON, no RootFS key")
}
img.rawJSON = src
return img, nil
}