1package image // import "github.com/docker/docker/image"
2
3import (
4	"encoding/json"
5	"errors"
6	"io"
7	"runtime"
8	"strings"
9	"time"
10
11	"github.com/docker/docker/api/types/container"
12	"github.com/docker/docker/dockerversion"
13	"github.com/docker/docker/layer"
14	"github.com/opencontainers/go-digest"
15)
16
17// ID is the content-addressable ID of an image.
18type ID digest.Digest
19
20func (id ID) String() string {
21	return id.Digest().String()
22}
23
24// Digest converts ID into a digest
25func (id ID) Digest() digest.Digest {
26	return digest.Digest(id)
27}
28
29// IDFromDigest creates an ID from a digest
30func IDFromDigest(digest digest.Digest) ID {
31	return ID(digest)
32}
33
34// V1Image stores the V1 image configuration.
35type V1Image struct {
36	// ID is a unique 64 character identifier of the image
37	ID string `json:"id,omitempty"`
38	// Parent is the ID of the parent image
39	Parent string `json:"parent,omitempty"`
40	// Comment is the commit message that was set when committing the image
41	Comment string `json:"comment,omitempty"`
42	// Created is the timestamp at which the image was created
43	Created time.Time `json:"created"`
44	// Container is the id of the container used to commit
45	Container string `json:"container,omitempty"`
46	// ContainerConfig is the configuration of the container that is committed into the image
47	ContainerConfig container.Config `json:"container_config,omitempty"`
48	// DockerVersion specifies the version of Docker that was used to build the image
49	DockerVersion string `json:"docker_version,omitempty"`
50	// Author is the name of the author that was specified when committing the image
51	Author string `json:"author,omitempty"`
52	// Config is the configuration of the container received from the client
53	Config *container.Config `json:"config,omitempty"`
54	// Architecture is the hardware that the image is built and runs on
55	Architecture string `json:"architecture,omitempty"`
56	// OS is the operating system used to build and run the image
57	OS string `json:"os,omitempty"`
58	// Size is the total size of the image including all layers it is composed of
59	Size int64 `json:",omitempty"`
60}
61
62// Image stores the image configuration
63type Image struct {
64	V1Image
65	Parent     ID        `json:"parent,omitempty"`
66	RootFS     *RootFS   `json:"rootfs,omitempty"`
67	History    []History `json:"history,omitempty"`
68	OSVersion  string    `json:"os.version,omitempty"`
69	OSFeatures []string  `json:"os.features,omitempty"`
70
71	// rawJSON caches the immutable JSON associated with this image.
72	rawJSON []byte
73
74	// computedID is the ID computed from the hash of the image config.
75	// Not to be confused with the legacy V1 ID in V1Image.
76	computedID ID
77}
78
79// RawJSON returns the immutable JSON associated with the image.
80func (img *Image) RawJSON() []byte {
81	return img.rawJSON
82}
83
84// ID returns the image's content-addressable ID.
85func (img *Image) ID() ID {
86	return img.computedID
87}
88
89// ImageID stringifies ID.
90func (img *Image) ImageID() string {
91	return img.ID().String()
92}
93
94// RunConfig returns the image's container config.
95func (img *Image) RunConfig() *container.Config {
96	return img.Config
97}
98
99// BaseImgArch returns the image's architecture. If not populated, defaults to the host runtime arch.
100func (img *Image) BaseImgArch() string {
101	arch := img.Architecture
102	if arch == "" {
103		arch = runtime.GOARCH
104	}
105	return arch
106}
107
108// OperatingSystem returns the image's operating system. If not populated, defaults to the host runtime OS.
109func (img *Image) OperatingSystem() string {
110	os := img.OS
111	if os == "" {
112		os = runtime.GOOS
113	}
114	return os
115}
116
117// MarshalJSON serializes the image to JSON. It sorts the top-level keys so
118// that JSON that's been manipulated by a push/pull cycle with a legacy
119// registry won't end up with a different key order.
120func (img *Image) MarshalJSON() ([]byte, error) {
121	type MarshalImage Image
122
123	pass1, err := json.Marshal(MarshalImage(*img))
124	if err != nil {
125		return nil, err
126	}
127
128	var c map[string]*json.RawMessage
129	if err := json.Unmarshal(pass1, &c); err != nil {
130		return nil, err
131	}
132	return json.Marshal(c)
133}
134
135// ChildConfig is the configuration to apply to an Image to create a new
136// Child image. Other properties of the image are copied from the parent.
137type ChildConfig struct {
138	ContainerID     string
139	Author          string
140	Comment         string
141	DiffID          layer.DiffID
142	ContainerConfig *container.Config
143	Config          *container.Config
144}
145
146// NewChildImage creates a new Image as a child of this image.
147func NewChildImage(img *Image, child ChildConfig, os string) *Image {
148	isEmptyLayer := layer.IsEmpty(child.DiffID)
149	var rootFS *RootFS
150	if img.RootFS != nil {
151		rootFS = img.RootFS.Clone()
152	} else {
153		rootFS = NewRootFS()
154	}
155
156	if !isEmptyLayer {
157		rootFS.Append(child.DiffID)
158	}
159	imgHistory := NewHistory(
160		child.Author,
161		child.Comment,
162		strings.Join(child.ContainerConfig.Cmd, " "),
163		isEmptyLayer)
164
165	return &Image{
166		V1Image: V1Image{
167			DockerVersion:   dockerversion.Version,
168			Config:          child.Config,
169			Architecture:    img.BaseImgArch(),
170			OS:              os,
171			Container:       child.ContainerID,
172			ContainerConfig: *child.ContainerConfig,
173			Author:          child.Author,
174			Created:         imgHistory.Created,
175		},
176		RootFS:     rootFS,
177		History:    append(img.History, imgHistory),
178		OSFeatures: img.OSFeatures,
179		OSVersion:  img.OSVersion,
180	}
181}
182
183// History stores build commands that were used to create an image
184type History struct {
185	// Created is the timestamp at which the image was created
186	Created time.Time `json:"created"`
187	// Author is the name of the author that was specified when committing the image
188	Author string `json:"author,omitempty"`
189	// CreatedBy keeps the Dockerfile command used while building the image
190	CreatedBy string `json:"created_by,omitempty"`
191	// Comment is the commit message that was set when committing the image
192	Comment string `json:"comment,omitempty"`
193	// EmptyLayer is set to true if this history item did not generate a
194	// layer. Otherwise, the history item is associated with the next
195	// layer in the RootFS section.
196	EmptyLayer bool `json:"empty_layer,omitempty"`
197}
198
199// NewHistory creates a new history struct from arguments, and sets the created
200// time to the current time in UTC
201func NewHistory(author, comment, createdBy string, isEmptyLayer bool) History {
202	return History{
203		Author:     author,
204		Created:    time.Now().UTC(),
205		CreatedBy:  createdBy,
206		Comment:    comment,
207		EmptyLayer: isEmptyLayer,
208	}
209}
210
211// Exporter provides interface for loading and saving images
212type Exporter interface {
213	Load(io.ReadCloser, io.Writer, bool) error
214	// TODO: Load(net.Context, io.ReadCloser, <- chan StatusMessage) error
215	Save([]string, io.Writer) error
216}
217
218// NewFromJSON creates an Image configuration from json.
219func NewFromJSON(src []byte) (*Image, error) {
220	img := &Image{}
221
222	if err := json.Unmarshal(src, img); err != nil {
223		return nil, err
224	}
225	if img.RootFS == nil {
226		return nil, errors.New("invalid image JSON, no RootFS key")
227	}
228
229	img.rawJSON = src
230
231	return img, nil
232}
233