Browse code

Set labels on container create

Signed-off-by: Darren Shepherd <darren@rancher.com>

Darren Shepherd authored on 2015/01/07 09:04:10
Showing 8 changed files
... ...
@@ -90,6 +90,10 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
90 90
 			return nil
91 91
 		}
92 92
 
93
+		if !psFilters.MatchKVList("label", container.Config.Labels) {
94
+			return nil
95
+		}
96
+
93 97
 		if before != "" && !foundBefore {
94 98
 			if container.ID == beforeCont.ID {
95 99
 				foundBefore = true
... ...
@@ -157,6 +161,7 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
157 157
 			out.SetInt64("SizeRw", sizeRw)
158 158
 			out.SetInt64("SizeRootFs", sizeRootFs)
159 159
 		}
160
+		out.SetJson("Labels", container.Config.Labels)
160 161
 		outs.Add(out)
161 162
 		return nil
162 163
 	}
... ...
@@ -11,13 +11,17 @@ import (
11 11
 	"github.com/docker/docker/pkg/parsers/filters"
12 12
 )
13 13
 
14
-var acceptedImageFilterTags = map[string]struct{}{"dangling": {}}
14
+var acceptedImageFilterTags = map[string]struct{}{
15
+	"dangling": {},
16
+	"label":    {},
17
+}
15 18
 
16 19
 func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
17 20
 	var (
18 21
 		allImages   map[string]*image.Image
19 22
 		err         error
20 23
 		filt_tagged = true
24
+		filt_label  = false
21 25
 	)
22 26
 
23 27
 	imageFilters, err := filters.FromParam(job.Getenv("filters"))
... ...
@@ -38,6 +42,8 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
38 38
 		}
39 39
 	}
40 40
 
41
+	_, filt_label = imageFilters["label"]
42
+
41 43
 	if job.GetenvBool("all") && filt_tagged {
42 44
 		allImages, err = s.graph.Map()
43 45
 	} else {
... ...
@@ -68,6 +74,9 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
68 68
 			} else {
69 69
 				// get the boolean list for if only the untagged images are requested
70 70
 				delete(allImages, id)
71
+				if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
72
+					continue
73
+				}
71 74
 				if filt_tagged {
72 75
 					out := &engine.Env{}
73 76
 					out.SetJson("ParentId", image.Parent)
... ...
@@ -76,6 +85,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
76 76
 					out.SetInt64("Created", image.Created.Unix())
77 77
 					out.SetInt64("Size", image.Size)
78 78
 					out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
79
+					out.SetJson("Labels", image.ContainerConfig.Labels)
79 80
 					lookup[id] = out
80 81
 				}
81 82
 			}
... ...
@@ -90,8 +100,11 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
90 90
 	}
91 91
 
92 92
 	// Display images which aren't part of a repository/tag
93
-	if job.Getenv("filter") == "" {
93
+	if job.Getenv("filter") == "" || filt_label {
94 94
 		for _, image := range allImages {
95
+			if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
96
+				continue
97
+			}
95 98
 			out := &engine.Env{}
96 99
 			out.SetJson("ParentId", image.Parent)
97 100
 			out.SetList("RepoTags", []string{"<none>:<none>"})
... ...
@@ -99,6 +112,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
99 99
 			out.SetInt64("Created", image.Created.Unix())
100 100
 			out.SetInt64("Size", image.Size)
101 101
 			out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
102
+			out.SetJson("Labels", image.ContainerConfig.Labels)
102 103
 			outs.Add(out)
103 104
 		}
104 105
 	}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"encoding/json"
5 5
 	"os"
6 6
 	"os/exec"
7
+	"reflect"
7 8
 	"testing"
8 9
 	"time"
9 10
 
... ...
@@ -249,3 +250,57 @@ func TestCreateVolumesCreated(t *testing.T) {
249 249
 
250 250
 	logDone("create - volumes are created")
251 251
 }
252
+
253
+func TestCreateLabels(t *testing.T) {
254
+	name := "test_create_labels"
255
+	expected := map[string]string{"k1": "v1", "k2": "v2"}
256
+	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k1=v1", "--label", "k2=v2", "busybox")); err != nil {
257
+		t.Fatal(out, err)
258
+	}
259
+
260
+	actual := make(map[string]string)
261
+	err := inspectFieldAndMarshall(name, "Config.Labels", &actual)
262
+	if err != nil {
263
+		t.Fatal(err)
264
+	}
265
+
266
+	if !reflect.DeepEqual(expected, actual) {
267
+		t.Fatalf("Expected %s got %s", expected, actual)
268
+	}
269
+
270
+	deleteAllContainers()
271
+
272
+	logDone("create - labels")
273
+}
274
+
275
+func TestCreateLabelFromImage(t *testing.T) {
276
+	imageName := "testcreatebuildlabel"
277
+	defer deleteImages(imageName)
278
+	_, err := buildImage(imageName,
279
+		`FROM busybox
280
+		LABEL k1=v1 k2=v2`,
281
+		true)
282
+	if err != nil {
283
+		t.Fatal(err)
284
+	}
285
+
286
+	name := "test_create_labels_from_image"
287
+	expected := map[string]string{"k2": "x", "k3": "v3", "k1": "v1"}
288
+	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k2=x", "--label", "k3=v3", imageName)); err != nil {
289
+		t.Fatal(out, err)
290
+	}
291
+
292
+	actual := make(map[string]string)
293
+	err = inspectFieldAndMarshall(name, "Config.Labels", &actual)
294
+	if err != nil {
295
+		t.Fatal(err)
296
+	}
297
+
298
+	if !reflect.DeepEqual(expected, actual) {
299
+		t.Fatalf("Expected %s got %s", expected, actual)
300
+	}
301
+
302
+	deleteAllContainers()
303
+
304
+	logDone("create - labels from image")
305
+}
... ...
@@ -77,6 +77,60 @@ func TestImagesErrorWithInvalidFilterNameTest(t *testing.T) {
77 77
 	logDone("images - invalid filter name check working")
78 78
 }
79 79
 
80
+func TestImagesFilterLabel(t *testing.T) {
81
+	imageName1 := "images_filter_test1"
82
+	imageName2 := "images_filter_test2"
83
+	imageName3 := "images_filter_test3"
84
+	defer deleteAllContainers()
85
+	defer deleteImages(imageName1)
86
+	defer deleteImages(imageName2)
87
+	defer deleteImages(imageName3)
88
+	image1ID, err := buildImage(imageName1,
89
+		`FROM scratch
90
+		 LABEL match me`, true)
91
+	if err != nil {
92
+		t.Fatal(err)
93
+	}
94
+
95
+	image2ID, err := buildImage(imageName2,
96
+		`FROM scratch
97
+		 LABEL match="me too"`, true)
98
+	if err != nil {
99
+		t.Fatal(err)
100
+	}
101
+
102
+	image3ID, err := buildImage(imageName3,
103
+		`FROM scratch
104
+		 LABEL nomatch me`, true)
105
+	if err != nil {
106
+		t.Fatal(err)
107
+	}
108
+
109
+	cmd := exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match")
110
+	out, _, err := runCommandWithOutput(cmd)
111
+	if err != nil {
112
+		t.Fatal(out, err)
113
+	}
114
+	out = strings.TrimSpace(out)
115
+
116
+	if (!strings.Contains(out, image1ID) && !strings.Contains(out, image2ID)) || strings.Contains(out, image3ID) {
117
+		t.Fatalf("Expected ids %s,%s got %s", image1ID, image2ID, out)
118
+	}
119
+
120
+	cmd = exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match=me too")
121
+	out, _, err = runCommandWithOutput(cmd)
122
+	if err != nil {
123
+		t.Fatal(out, err)
124
+	}
125
+	out = strings.TrimSpace(out)
126
+
127
+	if out != image2ID {
128
+		t.Fatalf("Expected %s got %s", image2ID, out)
129
+	}
130
+
131
+	logDone("images - filter label")
132
+}
133
+
80 134
 func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) {
81 135
 	imageName := "images_filter_test"
82 136
 	defer deleteAllContainers()
... ...
@@ -412,6 +412,54 @@ func TestPsListContainersFilterName(t *testing.T) {
412 412
 	logDone("ps - test ps filter name")
413 413
 }
414 414
 
415
+func TestPsListContainersFilterLabel(t *testing.T) {
416
+	// start container
417
+	runCmd := exec.Command(dockerBinary, "run", "-d", "-l", "match=me", "busybox")
418
+	out, _, err := runCommandWithOutput(runCmd)
419
+	if err != nil {
420
+		t.Fatal(out, err)
421
+	}
422
+	firstID := stripTrailingCharacters(out)
423
+
424
+	// start another container
425
+	runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "match=me too", "busybox")
426
+	if out, _, err = runCommandWithOutput(runCmd); err != nil {
427
+		t.Fatal(out, err)
428
+	}
429
+	secondID := stripTrailingCharacters(out)
430
+
431
+	// start third container
432
+	runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "nomatch=me", "busybox")
433
+	if out, _, err = runCommandWithOutput(runCmd); err != nil {
434
+		t.Fatal(out, err)
435
+	}
436
+	thirdID := stripTrailingCharacters(out)
437
+
438
+	// filter containers by exact match
439
+	runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me")
440
+	if out, _, err = runCommandWithOutput(runCmd); err != nil {
441
+		t.Fatal(out, err)
442
+	}
443
+	containerOut := strings.TrimSpace(out)
444
+	if containerOut != firstID {
445
+		t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out)
446
+	}
447
+
448
+	// filter containers by exact key
449
+	runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match")
450
+	if out, _, err = runCommandWithOutput(runCmd); err != nil {
451
+		t.Fatal(out, err)
452
+	}
453
+	containerOut = strings.TrimSpace(out)
454
+	if (!strings.Contains(containerOut, firstID) || !strings.Contains(containerOut, secondID)) || strings.Contains(containerOut, thirdID) {
455
+		t.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out)
456
+	}
457
+
458
+	deleteAllContainers()
459
+
460
+	logDone("ps - test ps filter label")
461
+}
462
+
415 463
 func TestPsListContainersFilterExited(t *testing.T) {
416 464
 	defer deleteAllContainers()
417 465
 
... ...
@@ -724,6 +724,15 @@ COPY . /static`); err != nil {
724 724
 		ctx:       ctx}, nil
725 725
 }
726 726
 
727
+func inspectFieldAndMarshall(name, field string, output interface{}) error {
728
+	str, err := inspectFieldJSON(name, field)
729
+	if err != nil {
730
+		return err
731
+	}
732
+
733
+	return json.Unmarshal([]byte(str), output)
734
+}
735
+
727 736
 func inspectFilter(name, filter string) (string, error) {
728 737
 	format := fmt.Sprintf("{{%s}}", filter)
729 738
 	inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name)
... ...
@@ -65,6 +65,38 @@ func FromParam(p string) (Args, error) {
65 65
 	return args, nil
66 66
 }
67 67
 
68
+func (filters Args) MatchKVList(field string, sources map[string]string) bool {
69
+	fieldValues := filters[field]
70
+
71
+	//do not filter if there is no filter set or cannot determine filter
72
+	if len(fieldValues) == 0 {
73
+		return true
74
+	}
75
+
76
+	if sources == nil || len(sources) == 0 {
77
+		return false
78
+	}
79
+
80
+outer:
81
+	for _, name2match := range fieldValues {
82
+		testKV := strings.SplitN(name2match, "=", 2)
83
+
84
+		for k, v := range sources {
85
+			if len(testKV) == 1 {
86
+				if k == testKV[0] {
87
+					continue outer
88
+				}
89
+			} else if k == testKV[0] && v == testKV[1] {
90
+				continue outer
91
+			}
92
+		}
93
+
94
+		return false
95
+	}
96
+
97
+	return true
98
+}
99
+
68 100
 func (filters Args) Match(field, source string) bool {
69 101
 	fieldValues := filters[field]
70 102
 
... ...
@@ -31,6 +31,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
31 31
 		flVolumes = opts.NewListOpts(opts.ValidatePath)
32 32
 		flLinks   = opts.NewListOpts(opts.ValidateLink)
33 33
 		flEnv     = opts.NewListOpts(opts.ValidateEnv)
34
+		flLabels  = opts.NewListOpts(opts.ValidateEnv)
34 35
 		flDevices = opts.NewListOpts(opts.ValidatePath)
35 36
 
36 37
 		ulimits   = make(map[string]*ulimit.Ulimit)
... ...
@@ -47,6 +48,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
47 47
 		flCapAdd      = opts.NewListOpts(nil)
48 48
 		flCapDrop     = opts.NewListOpts(nil)
49 49
 		flSecurityOpt = opts.NewListOpts(nil)
50
+		flLabelsFile  = opts.NewListOpts(nil)
50 51
 
51 52
 		flNetwork         = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
52 53
 		flPrivileged      = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
... ...
@@ -74,6 +76,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
74 74
 	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
75 75
 	cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container")
76 76
 	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
77
+	cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container, for example com.example.key=value")
78
+	cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
77 79
 	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
78 80
 	cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
79 81
 	cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
... ...
@@ -243,16 +247,16 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
243 243
 	}
244 244
 
245 245
 	// collect all the environment variables for the container
246
-	envVariables := []string{}
247
-	for _, ef := range flEnvFile.GetAll() {
248
-		parsedVars, err := opts.ParseEnvFile(ef)
249
-		if err != nil {
250
-			return nil, nil, cmd, err
251
-		}
252
-		envVariables = append(envVariables, parsedVars...)
246
+	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
247
+	if err != nil {
248
+		return nil, nil, cmd, err
249
+	}
250
+
251
+	// collect all the labels for the container
252
+	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
253
+	if err != nil {
254
+		return nil, nil, cmd, err
253 255
 	}
254
-	// parse the '-e' and '--env' after, to allow override
255
-	envVariables = append(envVariables, flEnv.GetAll()...)
256 256
 
257 257
 	ipcMode := IpcMode(*flIpcMode)
258 258
 	if !ipcMode.Valid() {
... ...
@@ -297,6 +301,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
297 297
 		MacAddress:      *flMacAddress,
298 298
 		Entrypoint:      entrypoint,
299 299
 		WorkingDir:      *flWorkingDir,
300
+		Labels:          convertKVStringsToMap(labels),
300 301
 	}
301 302
 
302 303
 	hostConfig := &HostConfig{
... ...
@@ -330,6 +335,37 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
330 330
 	return config, hostConfig, cmd, nil
331 331
 }
332 332
 
333
+// reads a file of line terminated key=value pairs and override that with override parameter
334
+func readKVStrings(files []string, override []string) ([]string, error) {
335
+	envVariables := []string{}
336
+	for _, ef := range files {
337
+		parsedVars, err := opts.ParseEnvFile(ef)
338
+		if err != nil {
339
+			return nil, err
340
+		}
341
+		envVariables = append(envVariables, parsedVars...)
342
+	}
343
+	// parse the '-e' and '--env' after, to allow override
344
+	envVariables = append(envVariables, override...)
345
+
346
+	return envVariables, nil
347
+}
348
+
349
+// converts ["key=value"] to {"key":"value"}
350
+func convertKVStringsToMap(values []string) map[string]string {
351
+	result := make(map[string]string, len(values))
352
+	for _, value := range values {
353
+		kv := strings.SplitN(value, "=", 2)
354
+		if len(kv) == 1 {
355
+			result[kv[0]] = ""
356
+		} else {
357
+			result[kv[0]] = kv[1]
358
+		}
359
+	}
360
+
361
+	return result
362
+}
363
+
333 364
 // parseRestartPolicy returns the parsed policy or an error indicating what is incorrect
334 365
 func parseRestartPolicy(policy string) (RestartPolicy, error) {
335 366
 	p := RestartPolicy{}