01214bafd2
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>
308 lines
8.5 KiB
Go
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
|
|
}
|