blob: 89799160daff67957f026b7bc56cc27a74deaed4 [file] [log] [blame]
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)
}