Skip to content

Commit 9f68710

Browse files
authored
feat(daemon): generate config file from Docker Engine API (#1130)
* feat(daemon): generate config file from Docker API * refactor: fix lint issues * test: add GraphDriver
1 parent d1c4e9f commit 9f68710

File tree

3 files changed

+193
-3
lines changed

3 files changed

+193
-3
lines changed

pkg/v1/daemon/image.go

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import (
1919
"context"
2020
"io"
2121
"sync"
22+
"time"
23+
24+
api "github.com/docker/docker/api/types"
25+
"github.com/docker/docker/api/types/container"
2226

2327
"github.com/google/go-containerregistry/pkg/name"
2428
v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -30,7 +34,9 @@ type image struct {
3034
ref name.Reference
3135
opener *imageOpener
3236
tarballImage v1.Image
37+
computed bool
3338
id *v1.Hash
39+
configFile *v1.ConfigFile
3440

3541
once sync.Once
3642
err error
@@ -121,6 +127,28 @@ func (i *image) initialize() error {
121127
return i.err
122128
}
123129

130+
func (i *image) compute() error {
131+
// Don't re-compute if already computed.
132+
if i.computed {
133+
return nil
134+
}
135+
136+
inspect, _, err := i.opener.client.ImageInspectWithRaw(i.opener.ctx, i.ref.String())
137+
if err != nil {
138+
return err
139+
}
140+
141+
configFile, err := i.computeConfigFile(inspect)
142+
if err != nil {
143+
return err
144+
}
145+
146+
i.configFile = configFile
147+
i.computed = true
148+
149+
return nil
150+
}
151+
124152
func (i *image) Layers() ([]v1.Layer, error) {
125153
if err := i.initialize(); err != nil {
126154
return nil, err
@@ -154,16 +182,19 @@ func (i *image) ConfigName() (v1.Hash, error) {
154182
}
155183

156184
func (i *image) ConfigFile() (*v1.ConfigFile, error) {
157-
if err := i.initialize(); err != nil {
185+
if err := i.compute(); err != nil {
158186
return nil, err
159187
}
160-
return i.tarballImage.ConfigFile()
188+
return i.configFile.DeepCopy(), nil
161189
}
162190

163191
func (i *image) RawConfigFile() ([]byte, error) {
164192
if err := i.initialize(); err != nil {
165193
return nil, err
166194
}
195+
196+
// RawConfigFile cannot be generated from "docker inspect" because Docker Engine API returns serialized data,
197+
// and formatting information of the raw config such as indent and prefix will be lost.
167198
return i.tarballImage.RawConfigFile()
168199
}
169200

@@ -201,3 +232,119 @@ func (i *image) LayerByDiffID(h v1.Hash) (v1.Layer, error) {
201232
}
202233
return i.tarballImage.LayerByDiffID(h)
203234
}
235+
236+
func (i *image) configHistory(author string) ([]v1.History, error) {
237+
historyItems, err := i.opener.client.ImageHistory(i.opener.ctx, i.ref.String())
238+
if err != nil {
239+
return nil, err
240+
}
241+
242+
history := make([]v1.History, len(historyItems))
243+
for j, h := range historyItems {
244+
history[j] = v1.History{
245+
Author: author,
246+
Created: v1.Time{
247+
Time: time.Unix(h.Created, 0).UTC(),
248+
},
249+
CreatedBy: h.CreatedBy,
250+
Comment: h.Comment,
251+
EmptyLayer: h.Size == 0,
252+
}
253+
}
254+
return history, nil
255+
}
256+
257+
func (i *image) diffIDs(rootFS api.RootFS) ([]v1.Hash, error) {
258+
diffIDs := make([]v1.Hash, len(rootFS.Layers))
259+
for j, l := range rootFS.Layers {
260+
h, err := v1.NewHash(l)
261+
if err != nil {
262+
return nil, err
263+
}
264+
diffIDs[j] = h
265+
}
266+
return diffIDs, nil
267+
}
268+
269+
func (i *image) computeConfigFile(inspect api.ImageInspect) (*v1.ConfigFile, error) {
270+
diffIDs, err := i.diffIDs(inspect.RootFS)
271+
if err != nil {
272+
return nil, err
273+
}
274+
275+
history, err := i.configHistory(inspect.Author)
276+
if err != nil {
277+
return nil, err
278+
}
279+
280+
created, err := time.Parse(time.RFC3339Nano, inspect.Created)
281+
if err != nil {
282+
return nil, err
283+
}
284+
285+
return &v1.ConfigFile{
286+
Architecture: inspect.Architecture,
287+
Author: inspect.Author,
288+
Container: inspect.Container,
289+
Created: v1.Time{Time: created},
290+
DockerVersion: inspect.DockerVersion,
291+
History: history,
292+
OS: inspect.Os,
293+
RootFS: v1.RootFS{
294+
Type: inspect.RootFS.Type,
295+
DiffIDs: diffIDs,
296+
},
297+
Config: i.computeImageConfig(inspect.Config),
298+
OSVersion: inspect.OsVersion,
299+
}, nil
300+
}
301+
302+
func (i *image) computeImageConfig(config *container.Config) v1.Config {
303+
if config == nil {
304+
return v1.Config{}
305+
}
306+
307+
c := v1.Config{
308+
AttachStderr: config.AttachStderr,
309+
AttachStdin: config.AttachStdin,
310+
AttachStdout: config.AttachStdout,
311+
Cmd: config.Cmd,
312+
Domainname: config.Domainname,
313+
Entrypoint: config.Entrypoint,
314+
Env: config.Env,
315+
Hostname: config.Hostname,
316+
Image: config.Image,
317+
Labels: config.Labels,
318+
OnBuild: config.OnBuild,
319+
OpenStdin: config.OpenStdin,
320+
StdinOnce: config.StdinOnce,
321+
Tty: config.Tty,
322+
User: config.User,
323+
Volumes: config.Volumes,
324+
WorkingDir: config.WorkingDir,
325+
ArgsEscaped: config.ArgsEscaped,
326+
NetworkDisabled: config.NetworkDisabled,
327+
MacAddress: config.MacAddress,
328+
StopSignal: config.StopSignal,
329+
Shell: config.Shell,
330+
}
331+
332+
if config.Healthcheck != nil {
333+
c.Healthcheck = &v1.HealthConfig{
334+
Test: config.Healthcheck.Test,
335+
Interval: config.Healthcheck.Interval,
336+
Timeout: config.Healthcheck.Timeout,
337+
StartPeriod: config.Healthcheck.StartPeriod,
338+
Retries: config.Healthcheck.Retries,
339+
}
340+
}
341+
342+
if len(config.ExposedPorts) > 0 {
343+
c.ExposedPorts = map[string]struct{}{}
344+
for port := range c.ExposedPorts {
345+
c.ExposedPorts[port] = struct{}{}
346+
}
347+
}
348+
349+
return c
350+
}

pkg/v1/daemon/image_test.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import (
2323
"strings"
2424
"testing"
2525

26+
"github.com/docker/docker/api/types/container"
27+
api "github.com/docker/docker/api/types/image"
28+
2629
"github.com/docker/docker/api/types"
2730
"github.com/google/go-containerregistry/internal/compare"
2831
"github.com/google/go-containerregistry/pkg/name"
@@ -62,12 +65,49 @@ func (m *MockClient) ImageSave(_ context.Context, _ []string) (io.ReadCloser, er
6265
return m.saveBody, m.saveErr
6366
}
6467

65-
func (m *MockClient) ImageInspectWithRaw(context.Context, string) (types.ImageInspect, []byte, error) {
68+
func (m *MockClient) ImageInspectWithRaw(_ context.Context, _ string) (types.ImageInspect, []byte, error) {
6669
return types.ImageInspect{
6770
ID: "sha256:6e0b05049ed9c17d02e1a55e80d6599dbfcce7f4f4b022e3c673e685789c470e",
71+
RepoTags: []string{
72+
"bazel/v1/tarball:test_image_1",
73+
},
74+
Created: "1970-01-01T00:00:00Z",
75+
Author: "Bazel",
76+
Architecture: "amd64",
77+
Os: "linux",
78+
Size: 8,
79+
VirtualSize: 8,
80+
Config: &container.Config{},
81+
GraphDriver: types.GraphDriverData{
82+
Data: map[string]string{
83+
"MergedDir": "/var/lib/docker/overlay2/988ecd005d048fd47b241dd57687231859563ba65a1dfd01ae1771ebfc4cb7c5/merged",
84+
"UpperDir": "/var/lib/docker/overlay2/988ecd005d048fd47b241dd57687231859563ba65a1dfd01ae1771ebfc4cb7c5/diff",
85+
"WorkDir": "/var/lib/docker/overlay2/988ecd005d048fd47b241dd57687231859563ba65a1dfd01ae1771ebfc4cb7c5/work",
86+
},
87+
Name: "overlay2",
88+
},
89+
RootFS: types.RootFS{
90+
Type: "layers",
91+
Layers: []string{
92+
"sha256:8897395fd26dc44ad0e2a834335b33198cb41ac4d98dfddf58eced3853fa7b17",
93+
},
94+
},
6895
}, nil, nil
6996
}
7097

98+
func (m *MockClient) ImageHistory(_ context.Context, _ string) ([]api.HistoryResponseItem, error) {
99+
return []api.HistoryResponseItem{
100+
{
101+
CreatedBy: "bazel build ...",
102+
ID: "sha256:6e0b05049ed9c17d02e1a55e80d6599dbfcce7f4f4b022e3c673e685789c470e",
103+
Size: 8,
104+
Tags: []string{
105+
"bazel/v1/tarball:test_image_1",
106+
},
107+
},
108+
}, nil
109+
}
110+
71111
func TestImage(t *testing.T) {
72112
for _, tc := range []struct {
73113
name string
@@ -120,6 +160,7 @@ func TestImage(t *testing.T) {
120160
}
121161
return
122162
}
163+
123164
err = compare.Images(img, dmn)
124165
if err != nil {
125166
if tc.wantErr == "" {

pkg/v1/daemon/options.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"io"
2020

2121
"github.com/docker/docker/api/types"
22+
api "github.com/docker/docker/api/types/image"
2223
"github.com/docker/docker/client"
2324
)
2425

@@ -100,4 +101,5 @@ type Client interface {
100101
ImageLoad(context.Context, io.Reader, bool) (types.ImageLoadResponse, error)
101102
ImageTag(context.Context, string, string) error
102103
ImageInspectWithRaw(context.Context, string) (types.ImageInspect, []byte, error)
104+
ImageHistory(context.Context, string) ([]api.HistoryResponseItem, error)
103105
}

0 commit comments

Comments
 (0)