Browse code

Extract the daemon image cache to its own package

ImageCache is now independent of `Daemon` and is located in
`image/cache` package.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

Vincent Demeester authored on 2017/01/04 03:19:27
Showing 6 changed files
... ...
@@ -1,69 +1,18 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
-	"encoding/json"
5
-	"fmt"
6
-	"reflect"
7
-	"strings"
8
-
9 4
 	"github.com/Sirupsen/logrus"
10
-	containertypes "github.com/docker/docker/api/types/container"
11 5
 	"github.com/docker/docker/builder"
12
-	"github.com/docker/docker/dockerversion"
13
-	"github.com/docker/docker/image"
14
-	"github.com/docker/docker/layer"
15
-	"github.com/docker/docker/runconfig"
16
-	"github.com/pkg/errors"
6
+	"github.com/docker/docker/image/cache"
17 7
 )
18 8
 
19
-// getLocalCachedImage returns the most recent created image that is a child
20
-// of the image with imgID, that had the same config when it was
21
-// created. nil is returned if a child cannot be found. An error is
22
-// returned if the parent image cannot be found.
23
-func (daemon *Daemon) getLocalCachedImage(imgID image.ID, config *containertypes.Config) (*image.Image, error) {
24
-	// Loop on the children of the given image and check the config
25
-	getMatch := func(siblings []image.ID) (*image.Image, error) {
26
-		var match *image.Image
27
-		for _, id := range siblings {
28
-			img, err := daemon.imageStore.Get(id)
29
-			if err != nil {
30
-				return nil, fmt.Errorf("unable to find image %q", id)
31
-			}
32
-
33
-			if runconfig.Compare(&img.ContainerConfig, config) {
34
-				// check for the most up to date match
35
-				if match == nil || match.Created.Before(img.Created) {
36
-					match = img
37
-				}
38
-			}
39
-		}
40
-		return match, nil
41
-	}
42
-
43
-	// In this case, this is `FROM scratch`, which isn't an actual image.
44
-	if imgID == "" {
45
-		images := daemon.imageStore.Map()
46
-		var siblings []image.ID
47
-		for id, img := range images {
48
-			if img.Parent == imgID {
49
-				siblings = append(siblings, id)
50
-			}
51
-		}
52
-		return getMatch(siblings)
53
-	}
54
-
55
-	// find match from child images
56
-	siblings := daemon.imageStore.Children(imgID)
57
-	return getMatch(siblings)
58
-}
59
-
60 9
 // MakeImageCache creates a stateful image cache.
61 10
 func (daemon *Daemon) MakeImageCache(sourceRefs []string) builder.ImageCache {
62 11
 	if len(sourceRefs) == 0 {
63
-		return &localImageCache{daemon}
12
+		return cache.NewLocal(daemon.imageStore)
64 13
 	}
65 14
 
66
-	cache := &imageCache{daemon: daemon, localImageCache: &localImageCache{daemon}}
15
+	cache := cache.New(daemon.imageStore)
67 16
 
68 17
 	for _, ref := range sourceRefs {
69 18
 		img, err := daemon.GetImage(ref)
... ...
@@ -71,184 +20,8 @@ func (daemon *Daemon) MakeImageCache(sourceRefs []string) builder.ImageCache {
71 71
 			logrus.Warnf("Could not look up %s for cache resolution, skipping: %+v", ref, err)
72 72
 			continue
73 73
 		}
74
-		cache.sources = append(cache.sources, img)
74
+		cache.Populate(img)
75 75
 	}
76 76
 
77 77
 	return cache
78 78
 }
79
-
80
-// localImageCache is cache based on parent chain.
81
-type localImageCache struct {
82
-	daemon *Daemon
83
-}
84
-
85
-func (lic *localImageCache) GetCache(imgID string, config *containertypes.Config) (string, error) {
86
-	return getImageIDAndError(lic.daemon.getLocalCachedImage(image.ID(imgID), config))
87
-}
88
-
89
-// imageCache is cache based on history objects. Requires initial set of images.
90
-type imageCache struct {
91
-	sources         []*image.Image
92
-	daemon          *Daemon
93
-	localImageCache *localImageCache
94
-}
95
-
96
-func (ic *imageCache) restoreCachedImage(parent, target *image.Image, cfg *containertypes.Config) (image.ID, error) {
97
-	var history []image.History
98
-	rootFS := image.NewRootFS()
99
-	lenHistory := 0
100
-	if parent != nil {
101
-		history = parent.History
102
-		rootFS = parent.RootFS
103
-		lenHistory = len(parent.History)
104
-	}
105
-	history = append(history, target.History[lenHistory])
106
-	if layer := getLayerForHistoryIndex(target, lenHistory); layer != "" {
107
-		rootFS.Append(layer)
108
-	}
109
-
110
-	config, err := json.Marshal(&image.Image{
111
-		V1Image: image.V1Image{
112
-			DockerVersion: dockerversion.Version,
113
-			Config:        cfg,
114
-			Architecture:  target.Architecture,
115
-			OS:            target.OS,
116
-			Author:        target.Author,
117
-			Created:       history[len(history)-1].Created,
118
-		},
119
-		RootFS:     rootFS,
120
-		History:    history,
121
-		OSFeatures: target.OSFeatures,
122
-		OSVersion:  target.OSVersion,
123
-	})
124
-	if err != nil {
125
-		return "", errors.Wrap(err, "failed to marshal image config")
126
-	}
127
-
128
-	imgID, err := ic.daemon.imageStore.Create(config)
129
-	if err != nil {
130
-		return "", errors.Wrap(err, "failed to create cache image")
131
-	}
132
-
133
-	if parent != nil {
134
-		if err := ic.daemon.imageStore.SetParent(imgID, parent.ID()); err != nil {
135
-			return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID())
136
-		}
137
-	}
138
-	return imgID, nil
139
-}
140
-
141
-func (ic *imageCache) isParent(imgID, parentID image.ID) bool {
142
-	nextParent, err := ic.daemon.imageStore.GetParent(imgID)
143
-	if err != nil {
144
-		return false
145
-	}
146
-	if nextParent == parentID {
147
-		return true
148
-	}
149
-	return ic.isParent(nextParent, parentID)
150
-}
151
-
152
-func (ic *imageCache) GetCache(parentID string, cfg *containertypes.Config) (string, error) {
153
-	imgID, err := ic.localImageCache.GetCache(parentID, cfg)
154
-	if err != nil {
155
-		return "", err
156
-	}
157
-	if imgID != "" {
158
-		for _, s := range ic.sources {
159
-			if ic.isParent(s.ID(), image.ID(imgID)) {
160
-				return imgID, nil
161
-			}
162
-		}
163
-	}
164
-
165
-	var parent *image.Image
166
-	lenHistory := 0
167
-	if parentID != "" {
168
-		parent, err = ic.daemon.imageStore.Get(image.ID(parentID))
169
-		if err != nil {
170
-			return "", errors.Wrapf(err, "unable to find image %v", parentID)
171
-		}
172
-		lenHistory = len(parent.History)
173
-	}
174
-
175
-	for _, target := range ic.sources {
176
-		if !isValidParent(target, parent) || !isValidConfig(cfg, target.History[lenHistory]) {
177
-			continue
178
-		}
179
-
180
-		if len(target.History)-1 == lenHistory { // last
181
-			if parent != nil {
182
-				if err := ic.daemon.imageStore.SetParent(target.ID(), parent.ID()); err != nil {
183
-					return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID())
184
-				}
185
-			}
186
-			return target.ID().String(), nil
187
-		}
188
-
189
-		imgID, err := ic.restoreCachedImage(parent, target, cfg)
190
-		if err != nil {
191
-			return "", errors.Wrapf(err, "failed to restore cached image from %q to %v", parentID, target.ID())
192
-		}
193
-
194
-		ic.sources = []*image.Image{target} // avoid jumping to different target, tuned for safety atm
195
-		return imgID.String(), nil
196
-	}
197
-
198
-	return "", nil
199
-}
200
-
201
-func getImageIDAndError(img *image.Image, err error) (string, error) {
202
-	if img == nil || err != nil {
203
-		return "", err
204
-	}
205
-	return img.ID().String(), nil
206
-}
207
-
208
-func isValidParent(img, parent *image.Image) bool {
209
-	if len(img.History) == 0 {
210
-		return false
211
-	}
212
-	if parent == nil || len(parent.History) == 0 && len(parent.RootFS.DiffIDs) == 0 {
213
-		return true
214
-	}
215
-	if len(parent.History) >= len(img.History) {
216
-		return false
217
-	}
218
-	if len(parent.RootFS.DiffIDs) >= len(img.RootFS.DiffIDs) {
219
-		return false
220
-	}
221
-
222
-	for i, h := range parent.History {
223
-		if !reflect.DeepEqual(h, img.History[i]) {
224
-			return false
225
-		}
226
-	}
227
-	for i, d := range parent.RootFS.DiffIDs {
228
-		if d != img.RootFS.DiffIDs[i] {
229
-			return false
230
-		}
231
-	}
232
-	return true
233
-}
234
-
235
-func getLayerForHistoryIndex(image *image.Image, index int) layer.DiffID {
236
-	layerIndex := 0
237
-	for i, h := range image.History {
238
-		if i == index {
239
-			if h.EmptyLayer {
240
-				return ""
241
-			}
242
-			break
243
-		}
244
-		if !h.EmptyLayer {
245
-			layerIndex++
246
-		}
247
-	}
248
-	return image.RootFS.DiffIDs[layerIndex] // validate?
249
-}
250
-
251
-func isValidConfig(cfg *containertypes.Config, h image.History) bool {
252
-	// todo: make this format better than join that loses data
253
-	return strings.Join(cfg.Cmd, " ") == h.CreatedBy
254
-}
255 79
new file mode 100644
... ...
@@ -0,0 +1,253 @@
0
+package cache
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"reflect"
6
+	"strings"
7
+
8
+	containertypes "github.com/docker/docker/api/types/container"
9
+	"github.com/docker/docker/dockerversion"
10
+	"github.com/docker/docker/image"
11
+	"github.com/docker/docker/layer"
12
+	"github.com/pkg/errors"
13
+)
14
+
15
+// NewLocal returns a local image cache, based on parent chain
16
+func NewLocal(store image.Store) *LocalImageCache {
17
+	return &LocalImageCache{
18
+		store: store,
19
+	}
20
+}
21
+
22
+// LocalImageCache is cache based on parent chain.
23
+type LocalImageCache struct {
24
+	store image.Store
25
+}
26
+
27
+// GetCache returns the image id found in the cache
28
+func (lic *LocalImageCache) GetCache(imgID string, config *containertypes.Config) (string, error) {
29
+	return getImageIDAndError(getLocalCachedImage(lic.store, image.ID(imgID), config))
30
+}
31
+
32
+// New returns an image cache, based on history objects
33
+func New(store image.Store) *ImageCache {
34
+	return &ImageCache{
35
+		store:           store,
36
+		localImageCache: NewLocal(store),
37
+	}
38
+}
39
+
40
+// ImageCache is cache based on history objects. Requires initial set of images.
41
+type ImageCache struct {
42
+	sources         []*image.Image
43
+	store           image.Store
44
+	localImageCache *LocalImageCache
45
+}
46
+
47
+// Populate adds an image to the cache (to be queried later)
48
+func (ic *ImageCache) Populate(image *image.Image) {
49
+	ic.sources = append(ic.sources, image)
50
+}
51
+
52
+// GetCache returns the image id found in the cache
53
+func (ic *ImageCache) GetCache(parentID string, cfg *containertypes.Config) (string, error) {
54
+	imgID, err := ic.localImageCache.GetCache(parentID, cfg)
55
+	if err != nil {
56
+		return "", err
57
+	}
58
+	if imgID != "" {
59
+		for _, s := range ic.sources {
60
+			if ic.isParent(s.ID(), image.ID(imgID)) {
61
+				return imgID, nil
62
+			}
63
+		}
64
+	}
65
+
66
+	var parent *image.Image
67
+	lenHistory := 0
68
+	if parentID != "" {
69
+		parent, err = ic.store.Get(image.ID(parentID))
70
+		if err != nil {
71
+			return "", errors.Wrapf(err, "unable to find image %v", parentID)
72
+		}
73
+		lenHistory = len(parent.History)
74
+	}
75
+
76
+	for _, target := range ic.sources {
77
+		if !isValidParent(target, parent) || !isValidConfig(cfg, target.History[lenHistory]) {
78
+			continue
79
+		}
80
+
81
+		if len(target.History)-1 == lenHistory { // last
82
+			if parent != nil {
83
+				if err := ic.store.SetParent(target.ID(), parent.ID()); err != nil {
84
+					return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID())
85
+				}
86
+			}
87
+			return target.ID().String(), nil
88
+		}
89
+
90
+		imgID, err := ic.restoreCachedImage(parent, target, cfg)
91
+		if err != nil {
92
+			return "", errors.Wrapf(err, "failed to restore cached image from %q to %v", parentID, target.ID())
93
+		}
94
+
95
+		ic.sources = []*image.Image{target} // avoid jumping to different target, tuned for safety atm
96
+		return imgID.String(), nil
97
+	}
98
+
99
+	return "", nil
100
+}
101
+
102
+func (ic *ImageCache) restoreCachedImage(parent, target *image.Image, cfg *containertypes.Config) (image.ID, error) {
103
+	var history []image.History
104
+	rootFS := image.NewRootFS()
105
+	lenHistory := 0
106
+	if parent != nil {
107
+		history = parent.History
108
+		rootFS = parent.RootFS
109
+		lenHistory = len(parent.History)
110
+	}
111
+	history = append(history, target.History[lenHistory])
112
+	if layer := getLayerForHistoryIndex(target, lenHistory); layer != "" {
113
+		rootFS.Append(layer)
114
+	}
115
+
116
+	config, err := json.Marshal(&image.Image{
117
+		V1Image: image.V1Image{
118
+			DockerVersion: dockerversion.Version,
119
+			Config:        cfg,
120
+			Architecture:  target.Architecture,
121
+			OS:            target.OS,
122
+			Author:        target.Author,
123
+			Created:       history[len(history)-1].Created,
124
+		},
125
+		RootFS:     rootFS,
126
+		History:    history,
127
+		OSFeatures: target.OSFeatures,
128
+		OSVersion:  target.OSVersion,
129
+	})
130
+	if err != nil {
131
+		return "", errors.Wrap(err, "failed to marshal image config")
132
+	}
133
+
134
+	imgID, err := ic.store.Create(config)
135
+	if err != nil {
136
+		return "", errors.Wrap(err, "failed to create cache image")
137
+	}
138
+
139
+	if parent != nil {
140
+		if err := ic.store.SetParent(imgID, parent.ID()); err != nil {
141
+			return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID())
142
+		}
143
+	}
144
+	return imgID, nil
145
+}
146
+
147
+func (ic *ImageCache) isParent(imgID, parentID image.ID) bool {
148
+	nextParent, err := ic.store.GetParent(imgID)
149
+	if err != nil {
150
+		return false
151
+	}
152
+	if nextParent == parentID {
153
+		return true
154
+	}
155
+	return ic.isParent(nextParent, parentID)
156
+}
157
+
158
+func getLayerForHistoryIndex(image *image.Image, index int) layer.DiffID {
159
+	layerIndex := 0
160
+	for i, h := range image.History {
161
+		if i == index {
162
+			if h.EmptyLayer {
163
+				return ""
164
+			}
165
+			break
166
+		}
167
+		if !h.EmptyLayer {
168
+			layerIndex++
169
+		}
170
+	}
171
+	return image.RootFS.DiffIDs[layerIndex] // validate?
172
+}
173
+
174
+func isValidConfig(cfg *containertypes.Config, h image.History) bool {
175
+	// todo: make this format better than join that loses data
176
+	return strings.Join(cfg.Cmd, " ") == h.CreatedBy
177
+}
178
+
179
+func isValidParent(img, parent *image.Image) bool {
180
+	if len(img.History) == 0 {
181
+		return false
182
+	}
183
+	if parent == nil || len(parent.History) == 0 && len(parent.RootFS.DiffIDs) == 0 {
184
+		return true
185
+	}
186
+	if len(parent.History) >= len(img.History) {
187
+		return false
188
+	}
189
+	if len(parent.RootFS.DiffIDs) >= len(img.RootFS.DiffIDs) {
190
+		return false
191
+	}
192
+
193
+	for i, h := range parent.History {
194
+		if !reflect.DeepEqual(h, img.History[i]) {
195
+			return false
196
+		}
197
+	}
198
+	for i, d := range parent.RootFS.DiffIDs {
199
+		if d != img.RootFS.DiffIDs[i] {
200
+			return false
201
+		}
202
+	}
203
+	return true
204
+}
205
+
206
+func getImageIDAndError(img *image.Image, err error) (string, error) {
207
+	if img == nil || err != nil {
208
+		return "", err
209
+	}
210
+	return img.ID().String(), nil
211
+}
212
+
213
+// getLocalCachedImage returns the most recent created image that is a child
214
+// of the image with imgID, that had the same config when it was
215
+// created. nil is returned if a child cannot be found. An error is
216
+// returned if the parent image cannot be found.
217
+func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *containertypes.Config) (*image.Image, error) {
218
+	// Loop on the children of the given image and check the config
219
+	getMatch := func(siblings []image.ID) (*image.Image, error) {
220
+		var match *image.Image
221
+		for _, id := range siblings {
222
+			img, err := imageStore.Get(id)
223
+			if err != nil {
224
+				return nil, fmt.Errorf("unable to find image %q", id)
225
+			}
226
+
227
+			if compare(&img.ContainerConfig, config) {
228
+				// check for the most up to date match
229
+				if match == nil || match.Created.Before(img.Created) {
230
+					match = img
231
+				}
232
+			}
233
+		}
234
+		return match, nil
235
+	}
236
+
237
+	// In this case, this is `FROM scratch`, which isn't an actual image.
238
+	if imgID == "" {
239
+		images := imageStore.Map()
240
+		var siblings []image.ID
241
+		for id, img := range images {
242
+			if img.Parent == imgID {
243
+				siblings = append(siblings, id)
244
+			}
245
+		}
246
+		return getMatch(siblings)
247
+	}
248
+
249
+	// find match from child images
250
+	siblings := imageStore.Children(imgID)
251
+	return getMatch(siblings)
252
+}
0 253
new file mode 100644
... ...
@@ -0,0 +1,61 @@
0
+package cache
1
+
2
+import "github.com/docker/docker/api/types/container"
3
+
4
+// compare two Config struct. Do not compare the "Image" nor "Hostname" fields
5
+// If OpenStdin is set, then it differs
6
+func compare(a, b *container.Config) bool {
7
+	if a == nil || b == nil ||
8
+		a.OpenStdin || b.OpenStdin {
9
+		return false
10
+	}
11
+	if a.AttachStdout != b.AttachStdout ||
12
+		a.AttachStderr != b.AttachStderr ||
13
+		a.User != b.User ||
14
+		a.OpenStdin != b.OpenStdin ||
15
+		a.Tty != b.Tty {
16
+		return false
17
+	}
18
+
19
+	if len(a.Cmd) != len(b.Cmd) ||
20
+		len(a.Env) != len(b.Env) ||
21
+		len(a.Labels) != len(b.Labels) ||
22
+		len(a.ExposedPorts) != len(b.ExposedPorts) ||
23
+		len(a.Entrypoint) != len(b.Entrypoint) ||
24
+		len(a.Volumes) != len(b.Volumes) {
25
+		return false
26
+	}
27
+
28
+	for i := 0; i < len(a.Cmd); i++ {
29
+		if a.Cmd[i] != b.Cmd[i] {
30
+			return false
31
+		}
32
+	}
33
+	for i := 0; i < len(a.Env); i++ {
34
+		if a.Env[i] != b.Env[i] {
35
+			return false
36
+		}
37
+	}
38
+	for k, v := range a.Labels {
39
+		if v != b.Labels[k] {
40
+			return false
41
+		}
42
+	}
43
+	for k := range a.ExposedPorts {
44
+		if _, exists := b.ExposedPorts[k]; !exists {
45
+			return false
46
+		}
47
+	}
48
+
49
+	for i := 0; i < len(a.Entrypoint); i++ {
50
+		if a.Entrypoint[i] != b.Entrypoint[i] {
51
+			return false
52
+		}
53
+	}
54
+	for key := range a.Volumes {
55
+		if _, exists := b.Volumes[key]; !exists {
56
+			return false
57
+		}
58
+	}
59
+	return true
60
+}
0 61
new file mode 100644
... ...
@@ -0,0 +1,126 @@
0
+package cache
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/docker/docker/api/types/container"
6
+	"github.com/docker/docker/api/types/strslice"
7
+	"github.com/docker/go-connections/nat"
8
+)
9
+
10
+// Just to make life easier
11
+func newPortNoError(proto, port string) nat.Port {
12
+	p, _ := nat.NewPort(proto, port)
13
+	return p
14
+}
15
+
16
+func TestCompare(t *testing.T) {
17
+	ports1 := make(nat.PortSet)
18
+	ports1[newPortNoError("tcp", "1111")] = struct{}{}
19
+	ports1[newPortNoError("tcp", "2222")] = struct{}{}
20
+	ports2 := make(nat.PortSet)
21
+	ports2[newPortNoError("tcp", "3333")] = struct{}{}
22
+	ports2[newPortNoError("tcp", "4444")] = struct{}{}
23
+	ports3 := make(nat.PortSet)
24
+	ports3[newPortNoError("tcp", "1111")] = struct{}{}
25
+	ports3[newPortNoError("tcp", "2222")] = struct{}{}
26
+	ports3[newPortNoError("tcp", "5555")] = struct{}{}
27
+	volumes1 := make(map[string]struct{})
28
+	volumes1["/test1"] = struct{}{}
29
+	volumes2 := make(map[string]struct{})
30
+	volumes2["/test2"] = struct{}{}
31
+	volumes3 := make(map[string]struct{})
32
+	volumes3["/test1"] = struct{}{}
33
+	volumes3["/test3"] = struct{}{}
34
+	envs1 := []string{"ENV1=value1", "ENV2=value2"}
35
+	envs2 := []string{"ENV1=value1", "ENV3=value3"}
36
+	entrypoint1 := strslice.StrSlice{"/bin/sh", "-c"}
37
+	entrypoint2 := strslice.StrSlice{"/bin/sh", "-d"}
38
+	entrypoint3 := strslice.StrSlice{"/bin/sh", "-c", "echo"}
39
+	cmd1 := strslice.StrSlice{"/bin/sh", "-c"}
40
+	cmd2 := strslice.StrSlice{"/bin/sh", "-d"}
41
+	cmd3 := strslice.StrSlice{"/bin/sh", "-c", "echo"}
42
+	labels1 := map[string]string{"LABEL1": "value1", "LABEL2": "value2"}
43
+	labels2 := map[string]string{"LABEL1": "value1", "LABEL2": "value3"}
44
+	labels3 := map[string]string{"LABEL1": "value1", "LABEL2": "value2", "LABEL3": "value3"}
45
+
46
+	sameConfigs := map[*container.Config]*container.Config{
47
+		// Empty config
48
+		&container.Config{}: {},
49
+		// Does not compare hostname, domainname & image
50
+		&container.Config{
51
+			Hostname:   "host1",
52
+			Domainname: "domain1",
53
+			Image:      "image1",
54
+			User:       "user",
55
+		}: {
56
+			Hostname:   "host2",
57
+			Domainname: "domain2",
58
+			Image:      "image2",
59
+			User:       "user",
60
+		},
61
+		// only OpenStdin
62
+		&container.Config{OpenStdin: false}: {OpenStdin: false},
63
+		// only env
64
+		&container.Config{Env: envs1}: {Env: envs1},
65
+		// only cmd
66
+		&container.Config{Cmd: cmd1}: {Cmd: cmd1},
67
+		// only labels
68
+		&container.Config{Labels: labels1}: {Labels: labels1},
69
+		// only exposedPorts
70
+		&container.Config{ExposedPorts: ports1}: {ExposedPorts: ports1},
71
+		// only entrypoints
72
+		&container.Config{Entrypoint: entrypoint1}: {Entrypoint: entrypoint1},
73
+		// only volumes
74
+		&container.Config{Volumes: volumes1}: {Volumes: volumes1},
75
+	}
76
+	differentConfigs := map[*container.Config]*container.Config{
77
+		nil: nil,
78
+		&container.Config{
79
+			Hostname:   "host1",
80
+			Domainname: "domain1",
81
+			Image:      "image1",
82
+			User:       "user1",
83
+		}: {
84
+			Hostname:   "host1",
85
+			Domainname: "domain1",
86
+			Image:      "image1",
87
+			User:       "user2",
88
+		},
89
+		// only OpenStdin
90
+		&container.Config{OpenStdin: false}: {OpenStdin: true},
91
+		&container.Config{OpenStdin: true}:  {OpenStdin: false},
92
+		// only env
93
+		&container.Config{Env: envs1}: {Env: envs2},
94
+		// only cmd
95
+		&container.Config{Cmd: cmd1}: {Cmd: cmd2},
96
+		// not the same number of parts
97
+		&container.Config{Cmd: cmd1}: {Cmd: cmd3},
98
+		// only labels
99
+		&container.Config{Labels: labels1}: {Labels: labels2},
100
+		// not the same number of labels
101
+		&container.Config{Labels: labels1}: {Labels: labels3},
102
+		// only exposedPorts
103
+		&container.Config{ExposedPorts: ports1}: {ExposedPorts: ports2},
104
+		// not the same number of ports
105
+		&container.Config{ExposedPorts: ports1}: {ExposedPorts: ports3},
106
+		// only entrypoints
107
+		&container.Config{Entrypoint: entrypoint1}: {Entrypoint: entrypoint2},
108
+		// not the same number of parts
109
+		&container.Config{Entrypoint: entrypoint1}: {Entrypoint: entrypoint3},
110
+		// only volumes
111
+		&container.Config{Volumes: volumes1}: {Volumes: volumes2},
112
+		// not the same number of labels
113
+		&container.Config{Volumes: volumes1}: {Volumes: volumes3},
114
+	}
115
+	for config1, config2 := range sameConfigs {
116
+		if !compare(config1, config2) {
117
+			t.Fatalf("Compare should be true for [%v] and [%v]", config1, config2)
118
+		}
119
+	}
120
+	for config1, config2 := range differentConfigs {
121
+		if compare(config1, config2) {
122
+			t.Fatalf("Compare should be false for [%v] and [%v]", config1, config2)
123
+		}
124
+	}
125
+}
0 126
deleted file mode 100644
... ...
@@ -1,61 +0,0 @@
1
-package runconfig
2
-
3
-import "github.com/docker/docker/api/types/container"
4
-
5
-// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
6
-// If OpenStdin is set, then it differs
7
-func Compare(a, b *container.Config) bool {
8
-	if a == nil || b == nil ||
9
-		a.OpenStdin || b.OpenStdin {
10
-		return false
11
-	}
12
-	if a.AttachStdout != b.AttachStdout ||
13
-		a.AttachStderr != b.AttachStderr ||
14
-		a.User != b.User ||
15
-		a.OpenStdin != b.OpenStdin ||
16
-		a.Tty != b.Tty {
17
-		return false
18
-	}
19
-
20
-	if len(a.Cmd) != len(b.Cmd) ||
21
-		len(a.Env) != len(b.Env) ||
22
-		len(a.Labels) != len(b.Labels) ||
23
-		len(a.ExposedPorts) != len(b.ExposedPorts) ||
24
-		len(a.Entrypoint) != len(b.Entrypoint) ||
25
-		len(a.Volumes) != len(b.Volumes) {
26
-		return false
27
-	}
28
-
29
-	for i := 0; i < len(a.Cmd); i++ {
30
-		if a.Cmd[i] != b.Cmd[i] {
31
-			return false
32
-		}
33
-	}
34
-	for i := 0; i < len(a.Env); i++ {
35
-		if a.Env[i] != b.Env[i] {
36
-			return false
37
-		}
38
-	}
39
-	for k, v := range a.Labels {
40
-		if v != b.Labels[k] {
41
-			return false
42
-		}
43
-	}
44
-	for k := range a.ExposedPorts {
45
-		if _, exists := b.ExposedPorts[k]; !exists {
46
-			return false
47
-		}
48
-	}
49
-
50
-	for i := 0; i < len(a.Entrypoint); i++ {
51
-		if a.Entrypoint[i] != b.Entrypoint[i] {
52
-			return false
53
-		}
54
-	}
55
-	for key := range a.Volumes {
56
-		if _, exists := b.Volumes[key]; !exists {
57
-			return false
58
-		}
59
-	}
60
-	return true
61
-}
62 1
deleted file mode 100644
... ...
@@ -1,126 +0,0 @@
1
-package runconfig
2
-
3
-import (
4
-	"testing"
5
-
6
-	"github.com/docker/docker/api/types/container"
7
-	"github.com/docker/docker/api/types/strslice"
8
-	"github.com/docker/go-connections/nat"
9
-)
10
-
11
-// Just to make life easier
12
-func newPortNoError(proto, port string) nat.Port {
13
-	p, _ := nat.NewPort(proto, port)
14
-	return p
15
-}
16
-
17
-func TestCompare(t *testing.T) {
18
-	ports1 := make(nat.PortSet)
19
-	ports1[newPortNoError("tcp", "1111")] = struct{}{}
20
-	ports1[newPortNoError("tcp", "2222")] = struct{}{}
21
-	ports2 := make(nat.PortSet)
22
-	ports2[newPortNoError("tcp", "3333")] = struct{}{}
23
-	ports2[newPortNoError("tcp", "4444")] = struct{}{}
24
-	ports3 := make(nat.PortSet)
25
-	ports3[newPortNoError("tcp", "1111")] = struct{}{}
26
-	ports3[newPortNoError("tcp", "2222")] = struct{}{}
27
-	ports3[newPortNoError("tcp", "5555")] = struct{}{}
28
-	volumes1 := make(map[string]struct{})
29
-	volumes1["/test1"] = struct{}{}
30
-	volumes2 := make(map[string]struct{})
31
-	volumes2["/test2"] = struct{}{}
32
-	volumes3 := make(map[string]struct{})
33
-	volumes3["/test1"] = struct{}{}
34
-	volumes3["/test3"] = struct{}{}
35
-	envs1 := []string{"ENV1=value1", "ENV2=value2"}
36
-	envs2 := []string{"ENV1=value1", "ENV3=value3"}
37
-	entrypoint1 := strslice.StrSlice{"/bin/sh", "-c"}
38
-	entrypoint2 := strslice.StrSlice{"/bin/sh", "-d"}
39
-	entrypoint3 := strslice.StrSlice{"/bin/sh", "-c", "echo"}
40
-	cmd1 := strslice.StrSlice{"/bin/sh", "-c"}
41
-	cmd2 := strslice.StrSlice{"/bin/sh", "-d"}
42
-	cmd3 := strslice.StrSlice{"/bin/sh", "-c", "echo"}
43
-	labels1 := map[string]string{"LABEL1": "value1", "LABEL2": "value2"}
44
-	labels2 := map[string]string{"LABEL1": "value1", "LABEL2": "value3"}
45
-	labels3 := map[string]string{"LABEL1": "value1", "LABEL2": "value2", "LABEL3": "value3"}
46
-
47
-	sameConfigs := map[*container.Config]*container.Config{
48
-		// Empty config
49
-		&container.Config{}: {},
50
-		// Does not compare hostname, domainname & image
51
-		&container.Config{
52
-			Hostname:   "host1",
53
-			Domainname: "domain1",
54
-			Image:      "image1",
55
-			User:       "user",
56
-		}: {
57
-			Hostname:   "host2",
58
-			Domainname: "domain2",
59
-			Image:      "image2",
60
-			User:       "user",
61
-		},
62
-		// only OpenStdin
63
-		&container.Config{OpenStdin: false}: {OpenStdin: false},
64
-		// only env
65
-		&container.Config{Env: envs1}: {Env: envs1},
66
-		// only cmd
67
-		&container.Config{Cmd: cmd1}: {Cmd: cmd1},
68
-		// only labels
69
-		&container.Config{Labels: labels1}: {Labels: labels1},
70
-		// only exposedPorts
71
-		&container.Config{ExposedPorts: ports1}: {ExposedPorts: ports1},
72
-		// only entrypoints
73
-		&container.Config{Entrypoint: entrypoint1}: {Entrypoint: entrypoint1},
74
-		// only volumes
75
-		&container.Config{Volumes: volumes1}: {Volumes: volumes1},
76
-	}
77
-	differentConfigs := map[*container.Config]*container.Config{
78
-		nil: nil,
79
-		&container.Config{
80
-			Hostname:   "host1",
81
-			Domainname: "domain1",
82
-			Image:      "image1",
83
-			User:       "user1",
84
-		}: {
85
-			Hostname:   "host1",
86
-			Domainname: "domain1",
87
-			Image:      "image1",
88
-			User:       "user2",
89
-		},
90
-		// only OpenStdin
91
-		&container.Config{OpenStdin: false}: {OpenStdin: true},
92
-		&container.Config{OpenStdin: true}:  {OpenStdin: false},
93
-		// only env
94
-		&container.Config{Env: envs1}: {Env: envs2},
95
-		// only cmd
96
-		&container.Config{Cmd: cmd1}: {Cmd: cmd2},
97
-		// not the same number of parts
98
-		&container.Config{Cmd: cmd1}: {Cmd: cmd3},
99
-		// only labels
100
-		&container.Config{Labels: labels1}: {Labels: labels2},
101
-		// not the same number of labels
102
-		&container.Config{Labels: labels1}: {Labels: labels3},
103
-		// only exposedPorts
104
-		&container.Config{ExposedPorts: ports1}: {ExposedPorts: ports2},
105
-		// not the same number of ports
106
-		&container.Config{ExposedPorts: ports1}: {ExposedPorts: ports3},
107
-		// only entrypoints
108
-		&container.Config{Entrypoint: entrypoint1}: {Entrypoint: entrypoint2},
109
-		// not the same number of parts
110
-		&container.Config{Entrypoint: entrypoint1}: {Entrypoint: entrypoint3},
111
-		// only volumes
112
-		&container.Config{Volumes: volumes1}: {Volumes: volumes2},
113
-		// not the same number of labels
114
-		&container.Config{Volumes: volumes1}: {Volumes: volumes3},
115
-	}
116
-	for config1, config2 := range sameConfigs {
117
-		if !Compare(config1, config2) {
118
-			t.Fatalf("Compare should be true for [%v] and [%v]", config1, config2)
119
-		}
120
-	}
121
-	for config1, config2 := range differentConfigs {
122
-		if Compare(config1, config2) {
123
-			t.Fatalf("Compare should be false for [%v] and [%v]", config1, config2)
124
-		}
125
-	}
126
-}