Browse Source

Set labels on container create

Signed-off-by: Darren Shepherd <darren@rancher.com>
Darren Shepherd 10 years ago
parent
commit
abb5e9a077

+ 5 - 0
daemon/list.go

@@ -90,6 +90,10 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
 			return nil
 			return nil
 		}
 		}
 
 
+		if !psFilters.MatchKVList("label", container.Config.Labels) {
+			return nil
+		}
+
 		if before != "" && !foundBefore {
 		if before != "" && !foundBefore {
 			if container.ID == beforeCont.ID {
 			if container.ID == beforeCont.ID {
 				foundBefore = true
 				foundBefore = true
@@ -157,6 +161,7 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
 			out.SetInt64("SizeRw", sizeRw)
 			out.SetInt64("SizeRw", sizeRw)
 			out.SetInt64("SizeRootFs", sizeRootFs)
 			out.SetInt64("SizeRootFs", sizeRootFs)
 		}
 		}
+		out.SetJson("Labels", container.Config.Labels)
 		outs.Add(out)
 		outs.Add(out)
 		return nil
 		return nil
 	}
 	}

+ 16 - 2
graph/list.go

@@ -11,13 +11,17 @@ import (
 	"github.com/docker/docker/pkg/parsers/filters"
 	"github.com/docker/docker/pkg/parsers/filters"
 )
 )
 
 
-var acceptedImageFilterTags = map[string]struct{}{"dangling": {}}
+var acceptedImageFilterTags = map[string]struct{}{
+	"dangling": {},
+	"label":    {},
+}
 
 
 func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
 func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
 	var (
 	var (
 		allImages   map[string]*image.Image
 		allImages   map[string]*image.Image
 		err         error
 		err         error
 		filt_tagged = true
 		filt_tagged = true
+		filt_label  = false
 	)
 	)
 
 
 	imageFilters, err := filters.FromParam(job.Getenv("filters"))
 	imageFilters, err := filters.FromParam(job.Getenv("filters"))
@@ -38,6 +42,8 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
 		}
 		}
 	}
 	}
 
 
+	_, filt_label = imageFilters["label"]
+
 	if job.GetenvBool("all") && filt_tagged {
 	if job.GetenvBool("all") && filt_tagged {
 		allImages, err = s.graph.Map()
 		allImages, err = s.graph.Map()
 	} else {
 	} else {
@@ -68,6 +74,9 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
 			} else {
 			} else {
 				// get the boolean list for if only the untagged images are requested
 				// get the boolean list for if only the untagged images are requested
 				delete(allImages, id)
 				delete(allImages, id)
+				if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
+					continue
+				}
 				if filt_tagged {
 				if filt_tagged {
 					out := &engine.Env{}
 					out := &engine.Env{}
 					out.SetJson("ParentId", image.Parent)
 					out.SetJson("ParentId", image.Parent)
@@ -76,6 +85,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
 					out.SetInt64("Created", image.Created.Unix())
 					out.SetInt64("Created", image.Created.Unix())
 					out.SetInt64("Size", image.Size)
 					out.SetInt64("Size", image.Size)
 					out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
 					out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
+					out.SetJson("Labels", image.ContainerConfig.Labels)
 					lookup[id] = out
 					lookup[id] = out
 				}
 				}
 			}
 			}
@@ -90,8 +100,11 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
 	}
 	}
 
 
 	// Display images which aren't part of a repository/tag
 	// Display images which aren't part of a repository/tag
-	if job.Getenv("filter") == "" {
+	if job.Getenv("filter") == "" || filt_label {
 		for _, image := range allImages {
 		for _, image := range allImages {
+			if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
+				continue
+			}
 			out := &engine.Env{}
 			out := &engine.Env{}
 			out.SetJson("ParentId", image.Parent)
 			out.SetJson("ParentId", image.Parent)
 			out.SetList("RepoTags", []string{"<none>:<none>"})
 			out.SetList("RepoTags", []string{"<none>:<none>"})
@@ -99,6 +112,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
 			out.SetInt64("Created", image.Created.Unix())
 			out.SetInt64("Created", image.Created.Unix())
 			out.SetInt64("Size", image.Size)
 			out.SetInt64("Size", image.Size)
 			out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
 			out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
+			out.SetJson("Labels", image.ContainerConfig.Labels)
 			outs.Add(out)
 			outs.Add(out)
 		}
 		}
 	}
 	}

+ 55 - 0
integration-cli/docker_cli_create_test.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
+	"reflect"
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
@@ -249,3 +250,57 @@ func TestCreateVolumesCreated(t *testing.T) {
 
 
 	logDone("create - volumes are created")
 	logDone("create - volumes are created")
 }
 }
+
+func TestCreateLabels(t *testing.T) {
+	name := "test_create_labels"
+	expected := map[string]string{"k1": "v1", "k2": "v2"}
+	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k1=v1", "--label", "k2=v2", "busybox")); err != nil {
+		t.Fatal(out, err)
+	}
+
+	actual := make(map[string]string)
+	err := inspectFieldAndMarshall(name, "Config.Labels", &actual)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !reflect.DeepEqual(expected, actual) {
+		t.Fatalf("Expected %s got %s", expected, actual)
+	}
+
+	deleteAllContainers()
+
+	logDone("create - labels")
+}
+
+func TestCreateLabelFromImage(t *testing.T) {
+	imageName := "testcreatebuildlabel"
+	defer deleteImages(imageName)
+	_, err := buildImage(imageName,
+		`FROM busybox
+		LABEL k1=v1 k2=v2`,
+		true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	name := "test_create_labels_from_image"
+	expected := map[string]string{"k2": "x", "k3": "v3", "k1": "v1"}
+	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k2=x", "--label", "k3=v3", imageName)); err != nil {
+		t.Fatal(out, err)
+	}
+
+	actual := make(map[string]string)
+	err = inspectFieldAndMarshall(name, "Config.Labels", &actual)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !reflect.DeepEqual(expected, actual) {
+		t.Fatalf("Expected %s got %s", expected, actual)
+	}
+
+	deleteAllContainers()
+
+	logDone("create - labels from image")
+}

+ 54 - 0
integration-cli/docker_cli_images_test.go

@@ -77,6 +77,60 @@ func TestImagesErrorWithInvalidFilterNameTest(t *testing.T) {
 	logDone("images - invalid filter name check working")
 	logDone("images - invalid filter name check working")
 }
 }
 
 
+func TestImagesFilterLabel(t *testing.T) {
+	imageName1 := "images_filter_test1"
+	imageName2 := "images_filter_test2"
+	imageName3 := "images_filter_test3"
+	defer deleteAllContainers()
+	defer deleteImages(imageName1)
+	defer deleteImages(imageName2)
+	defer deleteImages(imageName3)
+	image1ID, err := buildImage(imageName1,
+		`FROM scratch
+		 LABEL match me`, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	image2ID, err := buildImage(imageName2,
+		`FROM scratch
+		 LABEL match="me too"`, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	image3ID, err := buildImage(imageName3,
+		`FROM scratch
+		 LABEL nomatch me`, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	cmd := exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match")
+	out, _, err := runCommandWithOutput(cmd)
+	if err != nil {
+		t.Fatal(out, err)
+	}
+	out = strings.TrimSpace(out)
+
+	if (!strings.Contains(out, image1ID) && !strings.Contains(out, image2ID)) || strings.Contains(out, image3ID) {
+		t.Fatalf("Expected ids %s,%s got %s", image1ID, image2ID, out)
+	}
+
+	cmd = exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match=me too")
+	out, _, err = runCommandWithOutput(cmd)
+	if err != nil {
+		t.Fatal(out, err)
+	}
+	out = strings.TrimSpace(out)
+
+	if out != image2ID {
+		t.Fatalf("Expected %s got %s", image2ID, out)
+	}
+
+	logDone("images - filter label")
+}
+
 func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) {
 func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) {
 	imageName := "images_filter_test"
 	imageName := "images_filter_test"
 	defer deleteAllContainers()
 	defer deleteAllContainers()

+ 48 - 0
integration-cli/docker_cli_ps_test.go

@@ -412,6 +412,54 @@ func TestPsListContainersFilterName(t *testing.T) {
 	logDone("ps - test ps filter name")
 	logDone("ps - test ps filter name")
 }
 }
 
 
+func TestPsListContainersFilterLabel(t *testing.T) {
+	// start container
+	runCmd := exec.Command(dockerBinary, "run", "-d", "-l", "match=me", "busybox")
+	out, _, err := runCommandWithOutput(runCmd)
+	if err != nil {
+		t.Fatal(out, err)
+	}
+	firstID := stripTrailingCharacters(out)
+
+	// start another container
+	runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "match=me too", "busybox")
+	if out, _, err = runCommandWithOutput(runCmd); err != nil {
+		t.Fatal(out, err)
+	}
+	secondID := stripTrailingCharacters(out)
+
+	// start third container
+	runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "nomatch=me", "busybox")
+	if out, _, err = runCommandWithOutput(runCmd); err != nil {
+		t.Fatal(out, err)
+	}
+	thirdID := stripTrailingCharacters(out)
+
+	// filter containers by exact match
+	runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me")
+	if out, _, err = runCommandWithOutput(runCmd); err != nil {
+		t.Fatal(out, err)
+	}
+	containerOut := strings.TrimSpace(out)
+	if containerOut != firstID {
+		t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out)
+	}
+
+	// filter containers by exact key
+	runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match")
+	if out, _, err = runCommandWithOutput(runCmd); err != nil {
+		t.Fatal(out, err)
+	}
+	containerOut = strings.TrimSpace(out)
+	if (!strings.Contains(containerOut, firstID) || !strings.Contains(containerOut, secondID)) || strings.Contains(containerOut, thirdID) {
+		t.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out)
+	}
+
+	deleteAllContainers()
+
+	logDone("ps - test ps filter label")
+}
+
 func TestPsListContainersFilterExited(t *testing.T) {
 func TestPsListContainersFilterExited(t *testing.T) {
 	defer deleteAllContainers()
 	defer deleteAllContainers()
 
 

+ 9 - 0
integration-cli/docker_utils.go

@@ -724,6 +724,15 @@ COPY . /static`); err != nil {
 		ctx:       ctx}, nil
 		ctx:       ctx}, nil
 }
 }
 
 
+func inspectFieldAndMarshall(name, field string, output interface{}) error {
+	str, err := inspectFieldJSON(name, field)
+	if err != nil {
+		return err
+	}
+
+	return json.Unmarshal([]byte(str), output)
+}
+
 func inspectFilter(name, filter string) (string, error) {
 func inspectFilter(name, filter string) (string, error) {
 	format := fmt.Sprintf("{{%s}}", filter)
 	format := fmt.Sprintf("{{%s}}", filter)
 	inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name)
 	inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name)

+ 32 - 0
pkg/parsers/filters/parse.go

@@ -65,6 +65,38 @@ func FromParam(p string) (Args, error) {
 	return args, nil
 	return args, nil
 }
 }
 
 
+func (filters Args) MatchKVList(field string, sources map[string]string) bool {
+	fieldValues := filters[field]
+
+	//do not filter if there is no filter set or cannot determine filter
+	if len(fieldValues) == 0 {
+		return true
+	}
+
+	if sources == nil || len(sources) == 0 {
+		return false
+	}
+
+outer:
+	for _, name2match := range fieldValues {
+		testKV := strings.SplitN(name2match, "=", 2)
+
+		for k, v := range sources {
+			if len(testKV) == 1 {
+				if k == testKV[0] {
+					continue outer
+				}
+			} else if k == testKV[0] && v == testKV[1] {
+				continue outer
+			}
+		}
+
+		return false
+	}
+
+	return true
+}
+
 func (filters Args) Match(field, source string) bool {
 func (filters Args) Match(field, source string) bool {
 	fieldValues := filters[field]
 	fieldValues := filters[field]
 
 

+ 45 - 9
runconfig/parse.go

@@ -31,6 +31,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flVolumes = opts.NewListOpts(opts.ValidatePath)
 		flVolumes = opts.NewListOpts(opts.ValidatePath)
 		flLinks   = opts.NewListOpts(opts.ValidateLink)
 		flLinks   = opts.NewListOpts(opts.ValidateLink)
 		flEnv     = opts.NewListOpts(opts.ValidateEnv)
 		flEnv     = opts.NewListOpts(opts.ValidateEnv)
+		flLabels  = opts.NewListOpts(opts.ValidateEnv)
 		flDevices = opts.NewListOpts(opts.ValidatePath)
 		flDevices = opts.NewListOpts(opts.ValidatePath)
 
 
 		ulimits   = make(map[string]*ulimit.Ulimit)
 		ulimits   = make(map[string]*ulimit.Ulimit)
@@ -47,6 +48,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flCapAdd      = opts.NewListOpts(nil)
 		flCapAdd      = opts.NewListOpts(nil)
 		flCapDrop     = opts.NewListOpts(nil)
 		flCapDrop     = opts.NewListOpts(nil)
 		flSecurityOpt = opts.NewListOpts(nil)
 		flSecurityOpt = opts.NewListOpts(nil)
+		flLabelsFile  = opts.NewListOpts(nil)
 
 
 		flNetwork         = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
 		flNetwork         = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
 		flPrivileged      = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
 		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
 	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
 	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
 	cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container")
 	cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container")
 	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
 	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
+	cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container, for example com.example.key=value")
+	cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
 	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
 	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
 	cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
 	cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
 	cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
 	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
 	}
 	}
 
 
 	// collect all the environment variables for the container
 	// collect all the environment variables for the container
-	envVariables := []string{}
-	for _, ef := range flEnvFile.GetAll() {
-		parsedVars, err := opts.ParseEnvFile(ef)
-		if err != nil {
-			return nil, nil, cmd, err
-		}
-		envVariables = append(envVariables, parsedVars...)
+	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
+	if err != nil {
+		return nil, nil, cmd, err
+	}
+
+	// collect all the labels for the container
+	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
+	if err != nil {
+		return nil, nil, cmd, err
 	}
 	}
-	// parse the '-e' and '--env' after, to allow override
-	envVariables = append(envVariables, flEnv.GetAll()...)
 
 
 	ipcMode := IpcMode(*flIpcMode)
 	ipcMode := IpcMode(*flIpcMode)
 	if !ipcMode.Valid() {
 	if !ipcMode.Valid() {
@@ -297,6 +301,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		MacAddress:      *flMacAddress,
 		MacAddress:      *flMacAddress,
 		Entrypoint:      entrypoint,
 		Entrypoint:      entrypoint,
 		WorkingDir:      *flWorkingDir,
 		WorkingDir:      *flWorkingDir,
+		Labels:          convertKVStringsToMap(labels),
 	}
 	}
 
 
 	hostConfig := &HostConfig{
 	hostConfig := &HostConfig{
@@ -330,6 +335,37 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 	return config, hostConfig, cmd, nil
 	return config, hostConfig, cmd, nil
 }
 }
 
 
+// reads a file of line terminated key=value pairs and override that with override parameter
+func readKVStrings(files []string, override []string) ([]string, error) {
+	envVariables := []string{}
+	for _, ef := range files {
+		parsedVars, err := opts.ParseEnvFile(ef)
+		if err != nil {
+			return nil, err
+		}
+		envVariables = append(envVariables, parsedVars...)
+	}
+	// parse the '-e' and '--env' after, to allow override
+	envVariables = append(envVariables, override...)
+
+	return envVariables, nil
+}
+
+// converts ["key=value"] to {"key":"value"}
+func convertKVStringsToMap(values []string) map[string]string {
+	result := make(map[string]string, len(values))
+	for _, value := range values {
+		kv := strings.SplitN(value, "=", 2)
+		if len(kv) == 1 {
+			result[kv[0]] = ""
+		} else {
+			result[kv[0]] = kv[1]
+		}
+	}
+
+	return result
+}
+
 // parseRestartPolicy returns the parsed policy or an error indicating what is incorrect
 // parseRestartPolicy returns the parsed policy or an error indicating what is incorrect
 func parseRestartPolicy(policy string) (RestartPolicy, error) {
 func parseRestartPolicy(policy string) (RestartPolicy, error) {
 	p := RestartPolicy{}
 	p := RestartPolicy{}