From e1c05012cabca52565a69710b906433c8c930af6 Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Wed, 26 Aug 2015 13:06:45 +0000 Subject: [PATCH 1/3] Report docker container labels and render them in the details pane --- probe/docker/container.go | 10 ++++++++++ probe/docker/registry_test.go | 12 ++++++++++++ render/detailed_node.go | 12 ++++++++++++ render/detailed_node_test.go | 4 ++++ test/report_fixture.go | 9 +++++---- 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/probe/docker/container.go b/probe/docker/container.go index 700f408a5..2b1395134 100644 --- a/probe/docker/container.go +++ b/probe/docker/container.go @@ -27,6 +27,7 @@ const ( ContainerPorts = "docker_container_ports" ContainerCreated = "docker_container_created" ContainerIPs = "docker_container_ips" + ContainerLabels = "docker_container_labels" NetworkRxDropped = "network_rx_dropped" NetworkRxBytes = "network_rx_bytes" @@ -206,6 +207,7 @@ func (c *container) GetNodeMetadata() report.NodeMetadata { c.RLock() defer c.RUnlock() + labels, _ := json.Marshal(c.container.Config.Labels) result := report.MakeNodeMetadataWith(map[string]string{ ContainerID: c.ID(), ContainerName: strings.TrimPrefix(c.container.Name, "/"), @@ -215,6 +217,7 @@ func (c *container) GetNodeMetadata() report.NodeMetadata { ImageID: c.container.Image, ContainerIPs: strings.Join(append(c.container.NetworkSettings.SecondaryIPAddresses, c.container.NetworkSettings.IPAddress), " "), + ContainerLabels: string(labels), }) if c.latestStats == nil { @@ -245,6 +248,13 @@ func (c *container) GetNodeMetadata() report.NodeMetadata { return result } +// ExtractContainerLabels returns the list of Docker container labels given a NodeMetadata from the Container topology. +func ExtractContainerLabels(nmd report.NodeMetadata) map[string]string { + result := make(map[string]string) + json.Unmarshal([]byte(nmd.Metadata[ContainerLabels]), &result) + return result +} + // ExtractContainerIPs returns the list of container IPs given a NodeMetadata from the Container topology. func ExtractContainerIPs(nmd report.NodeMetadata) []string { return strings.Fields(nmd.Metadata[ContainerIPs]) diff --git a/probe/docker/registry_test.go b/probe/docker/registry_test.go index 240839426..a80ddb607 100644 --- a/probe/docker/registry_test.go +++ b/probe/docker/registry_test.go @@ -106,12 +106,24 @@ var ( NetworkSettings: &client.NetworkSettings{ IPAddress: "1.2.3.4", }, + Config: &client.Config{ + Labels: map[string]string{ + "foo1": "bar1", + "foo2": "bar2", + }, + }, } container2 = &client.Container{ ID: "wiff", Name: "waff", Image: "baz", State: client.State{Pid: 1, Running: true}, + Config: &client.Config{ + Labels: map[string]string{ + "foo1": "bar1", + "foo2": "bar2", + }, + }, } apiContainer1 = client.APIContainers{ID: "ping"} apiImage1 = client.APIImages{ID: "baz", RepoTags: []string{"bang", "not-chosen"}} diff --git a/render/detailed_node.go b/render/detailed_node.go index 263411b0e..cef17b65a 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -313,6 +313,18 @@ func containerOriginTable(nmd report.NodeMetadata, addHostTag bool) (Table, bool rows = append(rows, Row{Key: "IP Address", ValueMajor: ip, ValueMinor: ""}) } + // Add labels in alphabetical order + labels := docker.ExtractContainerLabels(nmd) + labelKeys := make([]string, 0, len(labels)) + for k := range labels { + labelKeys = append(labelKeys, k) + } + sort.Strings(labelKeys) + for _, key := range labelKeys { + rows = append(rows, Row{Key: fmt.Sprintf("Label %q", key), ValueMajor: labels[key]}) + + } + if val, ok := nmd.Metadata[docker.MemoryUsage]; ok { memory, err := strconv.ParseFloat(val, 64) if err == nil { diff --git a/render/detailed_node_test.go b/render/detailed_node_test.go index af34a06b7..f47c54064 100644 --- a/render/detailed_node_test.go +++ b/render/detailed_node_test.go @@ -58,6 +58,8 @@ func TestOriginTable(t *testing.T) { {"Host", test.ServerHostID, "", false}, {"ID", test.ServerContainerID, "", false}, {"Image ID", test.ServerContainerImageID, "", false}, + {`Label "foo1"`, `bar1`, "", false}, + {`Label "foo2"`, `bar2`, "", false}, }, }, } { @@ -146,6 +148,8 @@ func TestMakeDetailedContainerNode(t *testing.T) { Rows: []render.Row{ {"ID", test.ServerContainerID, "", false}, {"Image ID", test.ServerContainerImageID, "", false}, + {`Label "foo1"`, `bar1`, "", false}, + {`Label "foo2"`, `bar2`, "", false}, }, }, { diff --git a/test/report_fixture.go b/test/report_fixture.go index b84314239..31a2cb885 100644 --- a/test/report_fixture.go +++ b/test/report_fixture.go @@ -174,10 +174,11 @@ var ( report.HostNodeID: ClientHostNodeID, }), ServerContainerNodeID: report.MakeNodeMetadataWith(map[string]string{ - docker.ContainerID: ServerContainerID, - docker.ContainerName: "server", - docker.ImageID: ServerContainerImageID, - report.HostNodeID: ServerHostNodeID, + docker.ContainerID: ServerContainerID, + docker.ContainerName: "server", + docker.ImageID: ServerContainerImageID, + report.HostNodeID: ServerHostNodeID, + docker.ContainerLabels: `{"foo1": "bar1", "foo2": "bar2"}`, }), }, }, From 529aa3d84f4cc3f8fc869c1c5a547f74b9570e5f Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Wed, 26 Aug 2015 15:49:13 +0000 Subject: [PATCH 2/3] Use a metadata key per docker label --- probe/docker/container.go | 45 ++++++++++++++++++++++++++------------- render/detailed_node.go | 11 ++-------- test/report_fixture.go | 11 +++++----- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/probe/docker/container.go b/probe/docker/container.go index 2b1395134..db2c3e291 100644 --- a/probe/docker/container.go +++ b/probe/docker/container.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "sort" "strconv" "strings" "sync" @@ -22,12 +23,12 @@ import ( // These constants are keys used in node metadata const ( - ContainerName = "docker_container_name" - ContainerCommand = "docker_container_command" - ContainerPorts = "docker_container_ports" - ContainerCreated = "docker_container_created" - ContainerIPs = "docker_container_ips" - ContainerLabels = "docker_container_labels" + ContainerName = "docker_container_name" + ContainerCommand = "docker_container_command" + ContainerPorts = "docker_container_ports" + ContainerCreated = "docker_container_created" + ContainerIPs = "docker_container_ips" + ContainerLabelPrefix = "docker_container_label_" NetworkRxDropped = "network_rx_dropped" NetworkRxBytes = "network_rx_bytes" @@ -207,7 +208,6 @@ func (c *container) GetNodeMetadata() report.NodeMetadata { c.RLock() defer c.RUnlock() - labels, _ := json.Marshal(c.container.Config.Labels) result := report.MakeNodeMetadataWith(map[string]string{ ContainerID: c.ID(), ContainerName: strings.TrimPrefix(c.container.Name, "/"), @@ -217,9 +217,19 @@ func (c *container) GetNodeMetadata() report.NodeMetadata { ImageID: c.container.Image, ContainerIPs: strings.Join(append(c.container.NetworkSettings.SecondaryIPAddresses, c.container.NetworkSettings.IPAddress), " "), - ContainerLabels: string(labels), }) + // Add labels in alphabetical order + labels := c.container.Config.Labels + labelKeys := make([]string, 0, len(labels)) + for k := range labels { + labelKeys = append(labelKeys, k) + } + sort.Strings(labelKeys) + for _, labelKey := range labelKeys { + result.Metadata[ContainerLabelPrefix+labelKey] = labels[labelKey] + } + if c.latestStats == nil { return result } @@ -248,14 +258,19 @@ func (c *container) GetNodeMetadata() report.NodeMetadata { return result } -// ExtractContainerLabels returns the list of Docker container labels given a NodeMetadata from the Container topology. -func ExtractContainerLabels(nmd report.NodeMetadata) map[string]string { - result := make(map[string]string) - json.Unmarshal([]byte(nmd.Metadata[ContainerLabels]), &result) - return result -} - // ExtractContainerIPs returns the list of container IPs given a NodeMetadata from the Container topology. func ExtractContainerIPs(nmd report.NodeMetadata) []string { return strings.Fields(nmd.Metadata[ContainerIPs]) } + +// ExtractContainerLabels returns the list of Docker container labels given a NodeMetadata from the Container topology. +func ExtractContainerLabels(nmd report.NodeMetadata) map[string]string { + result := map[string]string{} + for key, value := range nmd.Metadata { + if strings.HasPrefix(key, ContainerLabelPrefix) { + label := key[len(ContainerLabelPrefix):] + result[label] = value + } + } + return result +} diff --git a/render/detailed_node.go b/render/detailed_node.go index cef17b65a..24bd51a6d 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -313,15 +313,8 @@ func containerOriginTable(nmd report.NodeMetadata, addHostTag bool) (Table, bool rows = append(rows, Row{Key: "IP Address", ValueMajor: ip, ValueMinor: ""}) } - // Add labels in alphabetical order - labels := docker.ExtractContainerLabels(nmd) - labelKeys := make([]string, 0, len(labels)) - for k := range labels { - labelKeys = append(labelKeys, k) - } - sort.Strings(labelKeys) - for _, key := range labelKeys { - rows = append(rows, Row{Key: fmt.Sprintf("Label %q", key), ValueMajor: labels[key]}) + for labelKey, labelValue := range docker.ExtractContainerLabels(nmd) { + rows = append(rows, Row{Key: fmt.Sprintf("Label %q", labelKey), ValueMajor: labelValue}) } diff --git a/test/report_fixture.go b/test/report_fixture.go index 31a2cb885..1c64cef40 100644 --- a/test/report_fixture.go +++ b/test/report_fixture.go @@ -174,11 +174,12 @@ var ( report.HostNodeID: ClientHostNodeID, }), ServerContainerNodeID: report.MakeNodeMetadataWith(map[string]string{ - docker.ContainerID: ServerContainerID, - docker.ContainerName: "server", - docker.ImageID: ServerContainerImageID, - report.HostNodeID: ServerHostNodeID, - docker.ContainerLabels: `{"foo1": "bar1", "foo2": "bar2"}`, + docker.ContainerID: ServerContainerID, + docker.ContainerName: "server", + docker.ImageID: ServerContainerImageID, + report.HostNodeID: ServerHostNodeID, + docker.ContainerLabelPrefix + "foo1": "bar1", + docker.ContainerLabelPrefix + "foo2": "bar2", }), }, }, From 9ba37402cfd172e24196c12b650c3d805aeb11e6 Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Wed, 26 Aug 2015 16:41:42 +0000 Subject: [PATCH 3/3] Report/render both image and container labels Daemon labels will have to wait since the go client (Docker API v1.14) doesn't support them yet (daemon labels were exposed in Docker API v1.16) See https://godoc.org/github.com/fsouza/go-dockerclient#Client.Info for details. --- probe/docker/container.go | 36 ++++++------------------------------ probe/docker/labels.go | 31 +++++++++++++++++++++++++++++++ probe/docker/labels_test.go | 25 +++++++++++++++++++++++++ probe/docker/reporter.go | 1 + render/detailed_node.go | 22 +++++++++++++++++----- test/report_fixture.go | 20 +++++++++++--------- 6 files changed, 91 insertions(+), 44 deletions(-) create mode 100644 probe/docker/labels.go create mode 100644 probe/docker/labels_test.go diff --git a/probe/docker/container.go b/probe/docker/container.go index db2c3e291..42bee395d 100644 --- a/probe/docker/container.go +++ b/probe/docker/container.go @@ -10,7 +10,6 @@ import ( "net/http" "net/http/httputil" "net/url" - "sort" "strconv" "strings" "sync" @@ -23,12 +22,11 @@ import ( // These constants are keys used in node metadata const ( - ContainerName = "docker_container_name" - ContainerCommand = "docker_container_command" - ContainerPorts = "docker_container_ports" - ContainerCreated = "docker_container_created" - ContainerIPs = "docker_container_ips" - ContainerLabelPrefix = "docker_container_label_" + ContainerName = "docker_container_name" + ContainerCommand = "docker_container_command" + ContainerPorts = "docker_container_ports" + ContainerCreated = "docker_container_created" + ContainerIPs = "docker_container_ips" NetworkRxDropped = "network_rx_dropped" NetworkRxBytes = "network_rx_bytes" @@ -218,17 +216,7 @@ func (c *container) GetNodeMetadata() report.NodeMetadata { ContainerIPs: strings.Join(append(c.container.NetworkSettings.SecondaryIPAddresses, c.container.NetworkSettings.IPAddress), " "), }) - - // Add labels in alphabetical order - labels := c.container.Config.Labels - labelKeys := make([]string, 0, len(labels)) - for k := range labels { - labelKeys = append(labelKeys, k) - } - sort.Strings(labelKeys) - for _, labelKey := range labelKeys { - result.Metadata[ContainerLabelPrefix+labelKey] = labels[labelKey] - } + AddLabels(result, c.container.Config.Labels) if c.latestStats == nil { return result @@ -262,15 +250,3 @@ func (c *container) GetNodeMetadata() report.NodeMetadata { func ExtractContainerIPs(nmd report.NodeMetadata) []string { return strings.Fields(nmd.Metadata[ContainerIPs]) } - -// ExtractContainerLabels returns the list of Docker container labels given a NodeMetadata from the Container topology. -func ExtractContainerLabels(nmd report.NodeMetadata) map[string]string { - result := map[string]string{} - for key, value := range nmd.Metadata { - if strings.HasPrefix(key, ContainerLabelPrefix) { - label := key[len(ContainerLabelPrefix):] - result[label] = value - } - } - return result -} diff --git a/probe/docker/labels.go b/probe/docker/labels.go new file mode 100644 index 000000000..2cc6a8dd4 --- /dev/null +++ b/probe/docker/labels.go @@ -0,0 +1,31 @@ +package docker + +import ( + "strings" + + "github.com/weaveworks/scope/report" +) + +// LabelPrefix is the key prefix used for Docker labels in NodeMetadata (e.g. a +// Docker label "labelKey"="labelValue" will get encoded as +// "docker_label_labelKey"="dockerValue" in the metadata) +const LabelPrefix = "docker_label_" + +// AddLabels appends Docker labels to the NodeMetadata from a topology. +func AddLabels(nmd report.NodeMetadata, labels map[string]string) { + for key, value := range labels { + nmd.Metadata[LabelPrefix+key] = value + } +} + +// ExtractLabels returns the list of Docker labels given a NodeMetadata from a topology. +func ExtractLabels(nmd report.NodeMetadata) map[string]string { + result := map[string]string{} + for key, value := range nmd.Metadata { + if strings.HasPrefix(key, LabelPrefix) { + label := key[len(LabelPrefix):] + result[label] = value + } + } + return result +} diff --git a/probe/docker/labels_test.go b/probe/docker/labels_test.go new file mode 100644 index 000000000..45c295e0d --- /dev/null +++ b/probe/docker/labels_test.go @@ -0,0 +1,25 @@ +package docker_test + +import ( + "reflect" + "testing" + + "github.com/weaveworks/scope/probe/docker" + "github.com/weaveworks/scope/report" + "github.com/weaveworks/scope/test" +) + +func TestLabels(t *testing.T) { + want := map[string]string{ + "foo1": "bar1", + "foo2": "bar2", + } + nmd := report.MakeNodeMetadata() + + docker.AddLabels(nmd, want) + have := docker.ExtractLabels(nmd) + + if !reflect.DeepEqual(want, have) { + t.Error(test.Diff(want, have)) + } +} diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go index 587fcff28..5f499557f 100644 --- a/probe/docker/reporter.go +++ b/probe/docker/reporter.go @@ -52,6 +52,7 @@ func (r *Reporter) containerImageTopology() report.Topology { nmd := report.MakeNodeMetadataWith(map[string]string{ ImageID: image.ID, }) + AddLabels(nmd, image.Labels) if len(image.RepoTags) > 0 { nmd.Metadata[ImageName] = image.RepoTags[0] diff --git a/render/detailed_node.go b/render/detailed_node.go index 24bd51a6d..c5fd927b9 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -312,11 +312,7 @@ func containerOriginTable(nmd report.NodeMetadata, addHostTag bool) (Table, bool for _, ip := range docker.ExtractContainerIPs(nmd) { rows = append(rows, Row{Key: "IP Address", ValueMajor: ip, ValueMinor: ""}) } - - for labelKey, labelValue := range docker.ExtractContainerLabels(nmd) { - rows = append(rows, Row{Key: fmt.Sprintf("Label %q", labelKey), ValueMajor: labelValue}) - - } + rows = append(rows, getDockerLabelRows(nmd)...) if val, ok := nmd.Metadata[docker.MemoryUsage]; ok { memory, err := strconv.ParseFloat(val, 64) @@ -354,6 +350,7 @@ func containerImageOriginTable(nmd report.NodeMetadata) (Table, bool) { rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""}) } } + rows = append(rows, getDockerLabelRows(nmd)...) title := "Container Image" var ( nameFound bool @@ -370,6 +367,21 @@ func containerImageOriginTable(nmd report.NodeMetadata) (Table, bool) { }, len(rows) > 0 || nameFound } +func getDockerLabelRows(nmd report.NodeMetadata) []Row { + rows := []Row{} + // Add labels in alphabetical order + labels := docker.ExtractLabels(nmd) + labelKeys := make([]string, 0, len(labels)) + for k := range labels { + labelKeys = append(labelKeys, k) + } + sort.Strings(labelKeys) + for _, labelKey := range labelKeys { + rows = append(rows, Row{Key: fmt.Sprintf("Label %q", labelKey), ValueMajor: labels[labelKey]}) + } + return rows +} + func hostOriginTable(nmd report.NodeMetadata) (Table, bool) { rows := []Row{} for _, tuple := range []struct{ key, human string }{ diff --git a/test/report_fixture.go b/test/report_fixture.go index 1c64cef40..cb9ac84a4 100644 --- a/test/report_fixture.go +++ b/test/report_fixture.go @@ -174,12 +174,12 @@ var ( report.HostNodeID: ClientHostNodeID, }), ServerContainerNodeID: report.MakeNodeMetadataWith(map[string]string{ - docker.ContainerID: ServerContainerID, - docker.ContainerName: "server", - docker.ImageID: ServerContainerImageID, - report.HostNodeID: ServerHostNodeID, - docker.ContainerLabelPrefix + "foo1": "bar1", - docker.ContainerLabelPrefix + "foo2": "bar2", + docker.ContainerID: ServerContainerID, + docker.ContainerName: "server", + docker.ImageID: ServerContainerImageID, + report.HostNodeID: ServerHostNodeID, + docker.LabelPrefix + "foo1": "bar1", + docker.LabelPrefix + "foo2": "bar2", }), }, }, @@ -191,9 +191,11 @@ var ( report.HostNodeID: ClientHostNodeID, }), ServerContainerImageNodeID: report.MakeNodeMetadataWith(map[string]string{ - docker.ImageID: ServerContainerImageID, - docker.ImageName: ServerContainerImageName, - report.HostNodeID: ServerHostNodeID, + docker.ImageID: ServerContainerImageID, + docker.ImageName: ServerContainerImageName, + report.HostNodeID: ServerHostNodeID, + docker.LabelPrefix + "foo1": "bar1", + docker.LabelPrefix + "foo2": "bar2", }), }, },