зеркало из https://github.com/microsoft/docker.git
150 строки
4.6 KiB
Go
150 строки
4.6 KiB
Go
package image
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/distribution/digest"
|
|
derr "github.com/docker/docker/errors"
|
|
"github.com/docker/docker/pkg/version"
|
|
"github.com/docker/docker/runconfig"
|
|
)
|
|
|
|
var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)
|
|
|
|
// noFallbackMinVersion is the minimum version for which v1compatibility
|
|
// information will not be marshaled through the Image struct to remove
|
|
// blank fields.
|
|
var noFallbackMinVersion = version.Version("1.8.3")
|
|
|
|
// Descriptor provides the information necessary to register an image in
|
|
// the graph.
|
|
type Descriptor interface {
|
|
ID() string
|
|
Parent() string
|
|
MarshalConfig() ([]byte, error)
|
|
}
|
|
|
|
// Image stores the image configuration.
|
|
// All fields in this struct must be marked `omitempty` to keep getting
|
|
// predictable hashes from the old `v1Compatibility` configuration.
|
|
type Image struct {
|
|
// ID a unique 64 character identifier of the image
|
|
ID string `json:"id,omitempty"`
|
|
// Parent id of the image
|
|
Parent string `json:"parent,omitempty"`
|
|
// Comment user added comment
|
|
Comment string `json:"comment,omitempty"`
|
|
// Created timestamp when image was created
|
|
Created time.Time `json:"created"`
|
|
// Container is the id of the container used to commit
|
|
Container string `json:"container,omitempty"`
|
|
// ContainerConfig is the configuration of the container that is committed into the image
|
|
ContainerConfig runconfig.Config `json:"container_config,omitempty"`
|
|
// DockerVersion specifies version on which image is built
|
|
DockerVersion string `json:"docker_version,omitempty"`
|
|
// Author of the image
|
|
Author string `json:"author,omitempty"`
|
|
// Config is the configuration of the container received from the client
|
|
Config *runconfig.Config `json:"config,omitempty"`
|
|
// Architecture is the hardware that the image is build and runs on
|
|
Architecture string `json:"architecture,omitempty"`
|
|
// OS is the operating system used to build and run the image
|
|
OS string `json:"os,omitempty"`
|
|
// Size is the total size of the image including all layers it is composed of
|
|
Size int64 `json:",omitempty"` // capitalized for backwards compatibility
|
|
// ParentID specifies the strong, content address of the parent configuration.
|
|
ParentID digest.Digest `json:"parent_id,omitempty"`
|
|
// LayerID provides the content address of the associated layer.
|
|
LayerID digest.Digest `json:"layer_id,omitempty"`
|
|
}
|
|
|
|
// NewImgJSON creates an Image configuration from json.
|
|
func NewImgJSON(src []byte) (*Image, error) {
|
|
ret := &Image{}
|
|
|
|
// FIXME: Is there a cleaner way to "purify" the input json?
|
|
if err := json.Unmarshal(src, ret); err != nil {
|
|
return nil, err
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// ValidateID checks whether an ID string is a valid image ID.
|
|
func ValidateID(id string) error {
|
|
if ok := validHex.MatchString(id); !ok {
|
|
return derr.ErrorCodeInvalidImageID.WithArgs(id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MakeImageConfig returns immutable configuration JSON for image based on the
|
|
// v1Compatibility object, layer digest and parent StrongID. SHA256() of this
|
|
// config is the new image ID (strongID).
|
|
func MakeImageConfig(v1Compatibility []byte, layerID, parentID digest.Digest) ([]byte, error) {
|
|
|
|
// Detect images created after 1.8.3
|
|
img, err := NewImgJSON(v1Compatibility)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
useFallback := version.Version(img.DockerVersion).LessThan(noFallbackMinVersion)
|
|
|
|
if useFallback {
|
|
// Fallback for pre-1.8.3. Calculate base config based on Image struct
|
|
// so that fields with default values added by Docker will use same ID
|
|
logrus.Debugf("Using fallback hash for %v", layerID)
|
|
|
|
v1Compatibility, err = json.Marshal(img)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var c map[string]*json.RawMessage
|
|
if err := json.Unmarshal(v1Compatibility, &c); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := layerID.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid layerID: %v", err)
|
|
}
|
|
|
|
c["layer_id"] = rawJSON(layerID)
|
|
|
|
if parentID != "" {
|
|
if err := parentID.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid parentID %v", err)
|
|
}
|
|
c["parent_id"] = rawJSON(parentID)
|
|
}
|
|
|
|
delete(c, "id")
|
|
delete(c, "parent")
|
|
delete(c, "Size") // Size is calculated from data on disk and is inconsitent
|
|
|
|
return json.Marshal(c)
|
|
}
|
|
|
|
// StrongID returns image ID for the config JSON.
|
|
func StrongID(configJSON []byte) (digest.Digest, error) {
|
|
digester := digest.Canonical.New()
|
|
if _, err := digester.Hash().Write(configJSON); err != nil {
|
|
return "", err
|
|
}
|
|
dgst := digester.Digest()
|
|
logrus.Debugf("H(%v) = %v", string(configJSON), dgst)
|
|
return dgst, nil
|
|
}
|
|
|
|
func rawJSON(value interface{}) *json.RawMessage {
|
|
jsonval, err := json.Marshal(value)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return (*json.RawMessage)(&jsonval)
|
|
}
|