diff --git a/client/app/scripts/components/node-details.js b/client/app/scripts/components/node-details.js
index 30b446d21f..bf8cd15060 100644
--- a/client/app/scripts/components/node-details.js
+++ b/client/app/scripts/components/node-details.js
@@ -191,11 +191,17 @@ export default class NodeDetails extends React.Component {
)}
- {details.docker_labels && details.docker_labels.length > 0
- &&
- {rows.map(field => (
-
- {field.label}
-
-
- {field.value}
+import ShowMore from '../show-more';
+
+export default class NodeDetailsLabels extends React.Component {
+
+ constructor(props, context) {
+ super(props, context);
+ this.DEFAULT_LIMIT = 5;
+ this.state = {
+ limit: this.DEFAULT_LIMIT,
+ };
+ this.handleLimitClick = this.handleLimitClick.bind(this);
+ }
+
+ handleLimitClick() {
+ const limit = this.state.limit ? 0 : this.DEFAULT_LIMIT;
+ this.setState({limit});
+ }
+
+ render() {
+ let rows = this.props.rows;
+ const limited = rows && this.state.limit > 0 && rows.length > this.state.limit;
+ const expanded = this.state.limit === 0;
+ const notShown = rows.length - this.DEFAULT_LIMIT;
+ if (rows && limited) {
+ rows = rows.slice(0, this.state.limit);
+ }
+
+ return (
+
+ {rows.map(field => (
+
+ {field.label}
+
+
+ {field.value}
+
-
- ))}
-
- );
+ ))}
+
+
+ );
+ }
}
diff --git a/probe/docker/container.go b/probe/docker/container.go
index 46e6849db7..61ce977191 100644
--- a/probe/docker/container.go
+++ b/probe/docker/container.go
@@ -65,6 +65,9 @@ const (
NetworkModeHost = "host"
+ LabelPrefix = "docker_label_"
+ EnvPrefix = "docker_env_"
+
stopTimeout = 10
)
@@ -323,6 +326,18 @@ func (c *container) metrics() report.Metrics {
return result
}
+func (c *container) env() map[string]string {
+ result := map[string]string{}
+ for _, value := range c.container.Config.Env {
+ v := strings.SplitN(value, "=", 2)
+ if len(v) != 2 {
+ continue
+ }
+ result[v[0]] = v[1]
+ }
+ return result
+}
+
func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node {
c.RLock()
defer c.RUnlock()
@@ -376,8 +391,8 @@ func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node {
result = result.WithControls(StartContainer)
}
- result = AddLabels(result, c.container.Config.Labels)
- result = AddEnv(result, c.container.Config.Env)
+ result = result.AddTable(LabelPrefix, c.container.Config.Labels)
+ result = result.AddTable(EnvPrefix, c.env())
result = result.WithMetrics(c.metrics())
return result
}
diff --git a/probe/docker/env.go b/probe/docker/env.go
deleted file mode 100644
index 1d216750b4..0000000000
--- a/probe/docker/env.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package docker
-
-import (
- "strings"
-
- "github.com/weaveworks/scope/report"
-)
-
-// EnvPrefix is the key prefix used for Docker environment variables in Node
-// (e.g. "TERM=vt200" will get encoded as "docker_env_TERM"="vt200" in the
-// metadata)
-const EnvPrefix = "docker_env_"
-
-// AddEnv appends Docker environment variables to the Node from a topology.
-func AddEnv(node report.Node, env []string) report.Node {
- node = node.Copy()
- for _, value := range env {
- v := strings.SplitN(value, "=", 2)
- if len(v) == 2 {
- key, value := v[0], v[1]
- node = node.WithLatests(map[string]string{
- EnvPrefix + key: value,
- })
- }
- }
- return node
-}
-
-// ExtractEnv returns the list of Docker environment variables given a Node from a topology.
-func ExtractEnv(node report.Node) map[string]string {
- result := map[string]string{}
- node.Latest.ForEach(func(key, value string) {
- if strings.HasPrefix(key, EnvPrefix) {
- env := key[len(EnvPrefix):]
- result[env] = value
- }
- })
- return result
-}
diff --git a/probe/docker/env_test.go b/probe/docker/env_test.go
deleted file mode 100644
index 64c68dad61..0000000000
--- a/probe/docker/env_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package docker_test
-
-import (
- "testing"
-
- "github.com/weaveworks/scope/probe/docker"
- "github.com/weaveworks/scope/report"
- _ "github.com/weaveworks/scope/test"
-)
-
-func TestEnv(t *testing.T) {
- given := []string{
- "TERM=vt200",
- "SHELL=/bin/ksh",
- "FOO1=\"foo=bar\"",
- "FOO2",
- }
- nmd := report.MakeNode("foo")
-
- nmd = docker.AddEnv(nmd, given)
- have := docker.ExtractEnv(nmd)
-
- if "vt200" != have["TERM"] {
- t.Errorf("Expected \"vt200\", got \"%s\"", have["TERM"])
- }
-
- if "/bin/ksh" != have["SHELL"] {
- t.Errorf("Expected \"/bin/ksh\", got \"%s\"", have["SHELL"])
- }
-
- if "\"foo=bar\"" != have["FOO1"] {
- t.Errorf("Expected \"\"foo=bar\"\", got \"%s\"", have["FOO1"])
- }
-
- if len(have) != 3 {
- t.Errorf("Expected only 3 items, got %d", len(have))
- }
-}
diff --git a/probe/docker/labels.go b/probe/docker/labels.go
deleted file mode 100644
index 3fd7899646..0000000000
--- a/probe/docker/labels.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package docker
-
-import (
- "strings"
-
- "github.com/weaveworks/scope/report"
-)
-
-// LabelPrefix is the key prefix used for Docker labels in Node (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 Node from a topology.
-func AddLabels(node report.Node, labels map[string]string) report.Node {
- node = node.Copy()
- for key, value := range labels {
- node = node.WithLatests(map[string]string{
- LabelPrefix + key: value,
- })
- }
- return node
-}
-
-// ExtractLabels returns the list of Docker labels given a Node from a topology.
-func ExtractLabels(node report.Node) map[string]string {
- result := map[string]string{}
- node.Latest.ForEach(func(key, value string) {
- if strings.HasPrefix(key, LabelPrefix) {
- label := key[len(LabelPrefix):]
- result[label] = value
- }
- })
- return result
-}
diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go
index facbb6673b..0b4ea8ff29 100644
--- a/probe/docker/reporter.go
+++ b/probe/docker/reporter.go
@@ -40,6 +40,15 @@ var (
ImageID: {ID: ImageID, Label: "Image ID", From: report.FromLatest, Truncate: 12, Priority: 1},
report.Container: {ID: report.Container, Label: "# Containers", From: report.FromCounters, Datatype: "number", Priority: 2},
}
+
+ ContainerTableTemplates = report.TableTemplates{
+ LabelPrefix: {ID: LabelPrefix, Label: "Docker Labels", Prefix: LabelPrefix},
+ EnvPrefix: {ID: EnvPrefix, Label: "Environment Variables", Prefix: EnvPrefix},
+ }
+
+ ContainerImageTableTemplates = report.TableTemplates{
+ LabelPrefix: {ID: LabelPrefix, Label: "Docker Labels", Prefix: LabelPrefix},
+ }
)
// Reporter generate Reports containing Container and ContainerImage topologies
@@ -96,7 +105,8 @@ func (r *Reporter) Report() (report.Report, error) {
func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology {
result := report.MakeTopology().
WithMetadataTemplates(ContainerMetadataTemplates).
- WithMetricTemplates(ContainerMetricTemplates)
+ WithMetricTemplates(ContainerMetricTemplates).
+ WithTableTemplates(ContainerTableTemplates)
result.Controls.AddControl(report.Control{
ID: StopContainer,
Human: "Stop",
@@ -143,7 +153,9 @@ func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology {
}
func (r *Reporter) containerImageTopology() report.Topology {
- result := report.MakeTopology().WithMetadataTemplates(ContainerImageMetadataTemplates)
+ result := report.MakeTopology().
+ WithMetadataTemplates(ContainerImageMetadataTemplates).
+ WithTableTemplates(ContainerImageTableTemplates)
r.registry.WalkImages(func(image *docker_client.APIImages) {
imageID := trimImageID(image.ID)
@@ -151,7 +163,7 @@ func (r *Reporter) containerImageTopology() report.Topology {
node := report.MakeNodeWith(nodeID, map[string]string{
ImageID: imageID,
})
- node = AddLabels(node, image.Labels)
+ node = node.AddTable(LabelPrefix, image.Labels)
if len(image.RepoTags) > 0 {
node = node.WithLatests(map[string]string{ImageName: image.RepoTags[0]})
diff --git a/probe/kubernetes/pod.go b/probe/kubernetes/pod.go
index 81a98f0419..0349ea87ce 100644
--- a/probe/kubernetes/pod.go
+++ b/probe/kubernetes/pod.go
@@ -16,6 +16,7 @@ const (
PodCreated = "kubernetes_pod_created"
PodContainerIDs = "kubernetes_pod_container_ids"
PodState = "kubernetes_pod_state"
+ PodLabelPrefix = "kubernetes_pod_labels_"
ServiceIDs = "kubernetes_service_ids"
)
@@ -99,5 +100,5 @@ func (p *pod) GetNode() report.Node {
Add(report.Service, report.MakeStringSet(report.MakeServiceNodeID(p.Namespace(), segments[1]))),
)
}
- return n
+ return n.AddTable(PodLabelPrefix, p.ObjectMeta.Labels)
}
diff --git a/probe/kubernetes/reporter.go b/probe/kubernetes/reporter.go
index c195e2421f..56e7293e1b 100644
--- a/probe/kubernetes/reporter.go
+++ b/probe/kubernetes/reporter.go
@@ -22,6 +22,14 @@ var (
ServiceCreated: {ID: ServiceCreated, Label: "Created", From: report.FromLatest, Priority: 3},
ServiceIP: {ID: ServiceIP, Label: "Internal IP", From: report.FromLatest, Priority: 4},
}
+
+ PodTableTemplates = report.TableTemplates{
+ PodLabelPrefix: {ID: PodLabelPrefix, Label: "Kubernetes Labels", Prefix: PodLabelPrefix},
+ }
+
+ ServiceTableTemplates = report.TableTemplates{
+ ServiceLabelPrefix: {ID: ServiceLabelPrefix, Label: "Kubernetes Labels", Prefix: ServiceLabelPrefix},
+ }
)
// Reporter generate Reports containing Container and ContainerImage topologies
@@ -58,7 +66,9 @@ func (r *Reporter) Report() (report.Report, error) {
func (r *Reporter) serviceTopology() (report.Topology, []Service, error) {
var (
- result = report.MakeTopology().WithMetadataTemplates(ServiceMetadataTemplates)
+ result = report.MakeTopology().
+ WithMetadataTemplates(ServiceMetadataTemplates).
+ WithTableTemplates(ServiceTableTemplates)
services = []Service{}
)
err := r.client.WalkServices(func(s Service) error {
@@ -71,7 +81,9 @@ func (r *Reporter) serviceTopology() (report.Topology, []Service, error) {
func (r *Reporter) podTopology(services []Service) (report.Topology, report.Topology, error) {
var (
- pods = report.MakeTopology().WithMetadataTemplates(PodMetadataTemplates)
+ pods = report.MakeTopology().
+ WithMetadataTemplates(PodMetadataTemplates).
+ WithTableTemplates(PodTableTemplates)
containers = report.MakeTopology()
selectors = map[string]labels.Selector{}
)
diff --git a/probe/kubernetes/service.go b/probe/kubernetes/service.go
index c1a73798b2..37460adf56 100644
--- a/probe/kubernetes/service.go
+++ b/probe/kubernetes/service.go
@@ -10,10 +10,11 @@ import (
// These constants are keys used in node metadata
const (
- ServiceID = "kubernetes_service_id"
- ServiceName = "kubernetes_service_name"
- ServiceCreated = "kubernetes_service_created"
- ServiceIP = "kubernetes_service_ip"
+ ServiceID = "kubernetes_service_id"
+ ServiceName = "kubernetes_service_name"
+ ServiceCreated = "kubernetes_service_created"
+ ServiceIP = "kubernetes_service_ip"
+ ServiceLabelPrefix = "kubernetes_service_label_"
)
// Service represents a Kubernetes service
@@ -60,5 +61,5 @@ func (s *service) GetNode() report.Node {
ServiceCreated: s.ObjectMeta.CreationTimestamp.Format(time.RFC822),
Namespace: s.Namespace(),
ServiceIP: s.Spec.ClusterIP,
- })
+ }).AddTable(ServiceLabelPrefix, s.Labels)
}
diff --git a/render/detailed/docker_labels.go b/render/detailed/docker_labels.go
deleted file mode 100644
index 0084475d86..0000000000
--- a/render/detailed/docker_labels.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package detailed
-
-import (
- "sort"
-
- "github.com/weaveworks/scope/probe/docker"
- "github.com/weaveworks/scope/report"
-)
-
-// NodeDockerLabels produces a table (to be consumed directly by the UI) based
-// on an origin ID, which is (optimistically) a node ID in one of our
-// topologies.
-func NodeDockerLabels(nmd report.Node) []report.MetadataRow {
- if _, ok := nmd.Counters.Lookup(nmd.Topology); ok {
- // This is a group of nodes, so no docker labels!
- return nil
- }
-
- if nmd.Topology != report.Container && nmd.Topology != report.ContainerImage {
- return nil
- }
-
- var rows []report.MetadataRow
- // 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, report.MetadataRow{
- ID: "label_" + labelKey,
- Label: labelKey,
- Value: labels[labelKey],
- })
- }
- return rows
-}
diff --git a/render/detailed/node_test.go b/render/detailed/node_test.go
index eec2c22fae..39a9b489cf 100644
--- a/render/detailed/node_test.go
+++ b/render/detailed/node_test.go
@@ -186,12 +186,6 @@ func TestMakeDetailedContainerNode(t *testing.T) {
{ID: "docker_container_state_human", Label: "State", Value: "running", Priority: 2},
{ID: "docker_image_id", Label: "Image ID", Value: fixture.ServerContainerImageID, Priority: 11},
},
- DockerLabels: []report.MetadataRow{
- {ID: "label_" + detailed.AmazonECSContainerNameLabel, Label: detailed.AmazonECSContainerNameLabel, Value: `server`},
- {ID: "label_foo1", Label: "foo1", Value: `bar1`},
- {ID: "label_foo2", Label: "foo2", Value: `bar2`},
- {ID: "label_io.kubernetes.pod.name", Label: "io.kubernetes.pod.name", Value: "ping/pong-b"},
- },
Metrics: []report.MetricRow{
{
ID: docker.CPUTotalUsage,
diff --git a/render/detailed/summary.go b/render/detailed/summary.go
index 4bc9c38a86..b657112b4f 100644
--- a/render/detailed/summary.go
+++ b/render/detailed/summary.go
@@ -61,18 +61,18 @@ type Column struct {
// NodeSummary is summary information about a child for a Node.
type NodeSummary struct {
- ID string `json:"id"`
- Label string `json:"label"`
- LabelMinor string `json:"label_minor"`
- Rank string `json:"rank"`
- Shape string `json:"shape,omitempty"`
- Stack bool `json:"stack,omitempty"`
- Linkable bool `json:"linkable,omitempty"` // Whether this node can be linked-to
- Pseudo bool `json:"pseudo,omitempty"`
- Metadata []report.MetadataRow `json:"metadata,omitempty"`
- DockerLabels []report.MetadataRow `json:"docker_labels,omitempty"`
- Metrics []report.MetricRow `json:"metrics,omitempty"`
- Adjacency report.IDList `json:"adjacency,omitempty"`
+ ID string `json:"id"`
+ Label string `json:"label"`
+ LabelMinor string `json:"label_minor"`
+ Rank string `json:"rank"`
+ Shape string `json:"shape,omitempty"`
+ Stack bool `json:"stack,omitempty"`
+ Linkable bool `json:"linkable,omitempty"` // Whether this node can be linked-to
+ Pseudo bool `json:"pseudo,omitempty"`
+ Metadata []report.MetadataRow `json:"metadata,omitempty"`
+ Metrics []report.MetricRow `json:"metrics,omitempty"`
+ Tables []report.Table `json:"tables,omitempty"`
+ Adjacency report.IDList `json:"adjacency,omitempty"`
}
// MakeNodeSummary summarizes a node, if possible.
@@ -117,8 +117,8 @@ func (n NodeSummary) Copy() NodeSummary {
for _, row := range n.Metadata {
result.Metadata = append(result.Metadata, row.Copy())
}
- for _, row := range n.DockerLabels {
- result.DockerLabels = append(result.DockerLabels, row.Copy())
+ for _, table := range n.Tables {
+ result.Tables = append(result.Tables, table.Copy())
}
for _, row := range n.Metrics {
result.Metrics = append(result.Metrics, row.Copy())
@@ -128,13 +128,13 @@ func (n NodeSummary) Copy() NodeSummary {
func baseNodeSummary(r report.Report, n report.Node) NodeSummary {
return NodeSummary{
- ID: n.ID,
- Shape: Circle,
- Linkable: true,
- Metadata: NodeMetadata(r, n),
- DockerLabels: NodeDockerLabels(n),
- Metrics: NodeMetrics(r, n),
- Adjacency: n.Adjacency.Copy(),
+ ID: n.ID,
+ Shape: Circle,
+ Linkable: true,
+ Metadata: NodeMetadata(r, n),
+ Metrics: NodeMetrics(r, n),
+ Tables: NodeTables(r, n),
+ Adjacency: n.Adjacency.Copy(),
}
}
diff --git a/render/detailed/tables.go b/render/detailed/tables.go
new file mode 100644
index 0000000000..63fe918784
--- /dev/null
+++ b/render/detailed/tables.go
@@ -0,0 +1,20 @@
+package detailed
+
+import (
+ "github.com/weaveworks/scope/report"
+)
+
+// NodeTables produces a list of tables (to be consumed directly by the UI) based
+// on the report and the node. It uses the report to get the templates for the node's
+// topology.
+func NodeTables(r report.Report, n report.Node) []report.Table {
+ if _, ok := n.Counters.Lookup(n.Topology); ok {
+ // This is a group of nodes, so no tables!
+ return nil
+ }
+
+ if topology, ok := r.Topology(n.Topology); ok {
+ return topology.TableTemplates.Tables(n)
+ }
+ return nil
+}
diff --git a/render/detailed/docker_labels_test.go b/render/detailed/tables_test.go
similarity index 64%
rename from render/detailed/docker_labels_test.go
rename to render/detailed/tables_test.go
index 8b8049f086..ebdf3b2ee8 100644
--- a/render/detailed/docker_labels_test.go
+++ b/render/detailed/tables_test.go
@@ -11,14 +11,19 @@ import (
"github.com/weaveworks/scope/test/fixture"
)
-func TestNodeDockerLabels(t *testing.T) {
+func TestNodeTables(t *testing.T) {
inputs := []struct {
name string
+ rpt report.Report
node report.Node
- want []report.MetadataRow
+ want []report.Table
}{
{
name: "container",
+ rpt: report.Report{
+ Container: report.MakeTopology().
+ WithTableTemplates(docker.ContainerTableTemplates),
+ },
node: report.MakeNodeWith(fixture.ClientContainerNodeID, map[string]string{
docker.ContainerID: fixture.ClientContainerID,
docker.LabelPrefix + "label1": "label1value",
@@ -26,16 +31,28 @@ func TestNodeDockerLabels(t *testing.T) {
}).WithTopology(report.Container).WithSets(report.EmptySets.
Add(docker.ContainerIPs, report.MakeStringSet("10.10.10.0/24", "10.10.10.1/24")),
),
- want: []report.MetadataRow{
+ want: []report.Table{
+ {
+ ID: docker.EnvPrefix,
+ Label: "Environment Variables",
+ Rows: []report.MetadataRow{},
+ },
{
- ID: "label_label1",
- Label: "label1",
- Value: "label1value",
+ ID: docker.LabelPrefix,
+ Label: "Docker Labels",
+ Rows: []report.MetadataRow{
+ {
+ ID: "label_label1",
+ Label: "label1",
+ Value: "label1value",
+ },
+ },
},
},
},
{
name: "unknown topology",
+ rpt: report.MakeReport(),
node: report.MakeNodeWith(fixture.ClientContainerNodeID, map[string]string{
docker.ContainerID: fixture.ClientContainerID,
}).WithTopology("foobar"),
@@ -43,7 +60,7 @@ func TestNodeDockerLabels(t *testing.T) {
},
}
for _, input := range inputs {
- have := detailed.NodeDockerLabels(input.node)
+ have := detailed.NodeTables(input.rpt, input.node)
if !reflect.DeepEqual(input.want, have) {
t.Errorf("%s: %s", input.name, test.Diff(input.want, have))
}
diff --git a/report/table.go b/report/table.go
new file mode 100644
index 0000000000..806a1b23c9
--- /dev/null
+++ b/report/table.go
@@ -0,0 +1,146 @@
+package report
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/weaveworks/scope/common/mtime"
+)
+
+// AddTable appends arbirary key-value pairs to the Node, returning a new node.
+func (node Node) AddTable(prefix string, labels map[string]string) Node {
+ for key, value := range labels {
+ node = node.WithLatest(prefix+key, mtime.Now(), value)
+ }
+ return node
+}
+
+// ExtractTable returns the key-value pairs with the given prefix from this Node,
+func (node Node) ExtractTable(prefix string) map[string]string {
+ result := map[string]string{}
+ node.Latest.ForEach(func(key, value string) {
+ if strings.HasPrefix(key, prefix) {
+ label := key[len(prefix):]
+ result[label] = value
+ }
+ })
+ return result
+}
+
+// Table is the type for a table in the UI.
+type Table struct {
+ ID string `json:"id"`
+ Label string `json:"label"`
+ Rows []MetadataRow `json:"rows"`
+}
+
+type tablesByID []Table
+
+func (t tablesByID) Len() int { return len(t) }
+func (t tablesByID) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
+func (t tablesByID) Less(i, j int) bool { return t[i].ID < t[j].ID }
+
+// Copy returns a copy of the Table.
+func (t Table) Copy() Table {
+ result := Table{
+ ID: t.ID,
+ Label: t.Label,
+ Rows: make([]MetadataRow, 0, len(t.Rows)),
+ }
+ for _, row := range t.Rows {
+ result.Rows = append(result.Rows, row)
+ }
+ return result
+}
+
+// TableTemplate describes how to render a table for the UI.
+type TableTemplate struct {
+ ID string `json:"id"`
+ Label string `json:"label"`
+ Prefix string `json:"prefix"`
+}
+
+// Copy returns a copy of the TableTemplate
+func (t TableTemplate) Copy() TableTemplate {
+ return TableTemplate{
+ ID: t.ID,
+ Label: t.Label,
+ Prefix: t.Prefix,
+ }
+}
+
+// Merge other into t, returning a fresh copy. Does fieldwise max -
+// whilst this isn't particularly meaningful, at least it idempotent,
+// commutativite and associative.
+func (t TableTemplate) Merge(other TableTemplate) TableTemplate {
+ max := func(s1, s2 string) string {
+ if s1 > s2 {
+ return s1
+ }
+ return s2
+ }
+
+ return TableTemplate{
+ ID: max(t.ID, other.ID),
+ Label: max(t.Label, other.Label),
+ Prefix: max(t.Prefix, other.Prefix),
+ }
+}
+
+// TableTemplates is a mergeable set of TableTemplate
+type TableTemplates map[string]TableTemplate
+
+// Tables renders the TableTemplates for a given node.
+func (t TableTemplates) Tables(node Node) []Table {
+ var result []Table
+ for _, template := range t {
+ table := Table{
+ ID: template.ID,
+ Label: template.Label,
+ Rows: []MetadataRow{},
+ }
+ rows := node.ExtractTable(template.Prefix)
+ keys := make([]string, 0, len(rows))
+ for k := range rows {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, key := range keys {
+ table.Rows = append(table.Rows, MetadataRow{
+ ID: "label_" + key,
+ Label: key,
+ Value: rows[key],
+ })
+ }
+ result = append(result, table)
+ }
+ sort.Sort(tablesByID(result))
+ return result
+}
+
+// Copy returns a value copy of the TableTemplates
+func (t TableTemplates) Copy() TableTemplates {
+ if t == nil {
+ return nil
+ }
+ result := TableTemplates{}
+ for k, v := range t {
+ result[k] = v.Copy()
+ }
+ return result
+}
+
+// Merge merges two sets of TableTemplates
+func (t TableTemplates) Merge(other TableTemplates) TableTemplates {
+ result := t.Copy()
+ for k, v := range other {
+ if result == nil {
+ result = TableTemplates{}
+ }
+ if existing, ok := result[k]; ok {
+ v = v.Merge(existing)
+ }
+ result[k] = v
+ }
+ return result
+}
diff --git a/probe/docker/labels_test.go b/report/table_test.go
similarity index 63%
rename from probe/docker/labels_test.go
rename to report/table_test.go
index 6db8fdfc0f..b1866b072a 100644
--- a/probe/docker/labels_test.go
+++ b/report/table_test.go
@@ -1,23 +1,22 @@
-package docker_test
+package report_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) {
+func TestTables(t *testing.T) {
want := map[string]string{
"foo1": "bar1",
"foo2": "bar2",
}
nmd := report.MakeNode("foo1")
- nmd = docker.AddLabels(nmd, want)
- have := docker.ExtractLabels(nmd)
+ nmd = nmd.AddTable("foo_", want)
+ have := nmd.ExtractTable("foo_")
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
diff --git a/report/topology.go b/report/topology.go
index 542573423b..da783e3b7c 100644
--- a/report/topology.go
+++ b/report/topology.go
@@ -14,6 +14,7 @@ type Topology struct {
Controls `json:"controls,omitempty"`
MetadataTemplates `json:"metadata_templates,omitempty"`
MetricTemplates `json:"metric_templates,omitempty"`
+ TableTemplates `json:"table_templates,omitempty"`
}
// MakeTopology gives you a Topology.
@@ -32,6 +33,7 @@ func (t Topology) WithMetadataTemplates(other MetadataTemplates) Topology {
Controls: t.Controls.Copy(),
MetadataTemplates: t.MetadataTemplates.Merge(other),
MetricTemplates: t.MetricTemplates.Copy(),
+ TableTemplates: t.TableTemplates.Copy(),
}
}
@@ -43,6 +45,19 @@ func (t Topology) WithMetricTemplates(other MetricTemplates) Topology {
Controls: t.Controls.Copy(),
MetadataTemplates: t.MetadataTemplates.Copy(),
MetricTemplates: t.MetricTemplates.Merge(other),
+ TableTemplates: t.TableTemplates.Copy(),
+ }
+}
+
+// WithTableTemplates merges some table templates into this topology,
+// returning a new topology.
+func (t Topology) WithTableTemplates(other TableTemplates) Topology {
+ return Topology{
+ Nodes: t.Nodes.Copy(),
+ Controls: t.Controls.Copy(),
+ MetadataTemplates: t.MetadataTemplates.Copy(),
+ MetricTemplates: t.MetricTemplates.Copy(),
+ TableTemplates: t.TableTemplates.Merge(other),
}
}
@@ -66,6 +81,7 @@ func (t Topology) Copy() Topology {
Controls: t.Controls.Copy(),
MetadataTemplates: t.MetadataTemplates.Copy(),
MetricTemplates: t.MetricTemplates.Copy(),
+ TableTemplates: t.TableTemplates.Copy(),
}
}
@@ -77,6 +93,7 @@ func (t Topology) Merge(other Topology) Topology {
Controls: t.Controls.Merge(other.Controls),
MetadataTemplates: t.MetadataTemplates.Merge(other.MetadataTemplates),
MetricTemplates: t.MetricTemplates.Merge(other.MetricTemplates),
+ TableTemplates: t.TableTemplates.Merge(other.TableTemplates),
}
}