| 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) |
| } |