diff --git a/CHANGELOG.md b/CHANGELOG.md index 109ea000d..5d882117a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,143 @@ +## Release 0.15.0 + +Highlights: + +This release comes with: + * Search: new smart search field that allows you to filter what you can see by + container names, all kinds of metadata, e.g., IP addresses, and metric + comparisons, e.g., CPU > 50%. + * Enhanced Kubernetes Visualization: namespace filters, ReplicaSet/Deployment + views, extra metadata, better navigation, show Pod logs, delete Pods, + bugfixes and more ... + * Scope App performance improvements: ~3X reduction in CPU consumption. + + +New features and enhancements: +- New search field + [#1429](https://github.com/weaveworks/scope/pull/1429) + [#1499](https://github.com/weaveworks/scope/pull/1499) +- Kubernetes improvements: + - Deployment and Replica Set views + [#1436](https://github.com/weaveworks/scope/pull/1436) + - Add scale up/down controls on deployments, replica sets, and replication controllers + [#1451](https://github.com/weaveworks/scope/pull/1451) + - Filter by Kubernetes Namespaces + [#1386](https://github.com/weaveworks/scope/pull/1386) + - Remove App->Probe deployment ordering restriction + [#1433](https://github.com/weaveworks/scope/pull/1433) + - Show Pod IP and # container in the children table in details panel. + [#1435](https://github.com/weaveworks/scope/pull/1435) + [#1409](https://github.com/weaveworks/scope/pull/1409) + - Add pod delete controls + [#1368](https://github.com/weaveworks/scope/pull/1368) + - Show the k8s load balancer IP if it is set + [#1378](https://github.com/weaveworks/scope/pull/1378) + - Show number of pods in service + [#1352](https://github.com/weaveworks/scope/pull/1352) + - Filter GKE system containers + [#1438](https://github.com/weaveworks/scope/pull/1438) +- Show k8s labels and container env vars in the details panel + [#1342](https://github.com/weaveworks/scope/pull/1342) + [#1465](https://github.com/weaveworks/scope/pull/1465) +- Implement `scope help` + [#1357](https://github.com/weaveworks/scope/pull/1357) + [#1419](https://github.com/weaveworks/scope/pull/1419) +- Add swarm-agent, swarm-agent master to system container filter + [#1356](https://github.com/weaveworks/scope/pull/1356) +- Add control for removing stopped docker containers. + [#1290](https://github.com/weaveworks/scope/pull/1290) +- Add a button to download the report as JSON + [#1365](https://github.com/weaveworks/scope/pull/1365) +- Use reverse-resolved DNS info in the connections table. + [#1359](https://github.com/weaveworks/scope/pull/1359) +- Add a 'Unmanaged' node to k8s views which included non-k8s containers. + [#1350](https://github.com/weaveworks/scope/pull/1350) +- Support docker rename events + [#1332](https://github.com/weaveworks/scope/pull/1332) +- Strip image version from parent links + [#1348](https://github.com/weaveworks/scope/pull/1348) +- Add Docker for Mac support + [#1448](https://github.com/weaveworks/scope/pull/1448) + +Performance improvements: +- Scope App: + - A log(n) complexity report merger + [#1418](https://github.com/weaveworks/scope/pull/1418) + [#1447](https://github.com/weaveworks/scope/pull/1447) + - Don't merge nodes in the rendering pipeline + [#1398](https://github.com/weaveworks/scope/pull/1398) + - Pass nil for the decorator in the rendering pipeline when possible + [#1397](https://github.com/weaveworks/scope/pull/1397) +- Scope Probe: + - Precompute base of the container nodes + [#1456](https://github.com/weaveworks/scope/pull/1456) + +Bug fixes: +- Correctly attribute DNAT-ed short-lived connections + [#1410](https://github.com/weaveworks/scope/pull/1410) +- Don't attribute conntracked connections to k8s pause containers. + [#1415](https://github.com/weaveworks/scope/pull/1415) +- Don't show kubernetes views if not running kubernetes + [#1364](https://github.com/weaveworks/scope/issues/1364) +- Missing pod names in kubernetes' pod view and Pause containers don't show as children of pods + [#1412](https://github.com/weaveworks/scope/pull/1412) +- Fix grouped node count for filtered children nodes + [#1371](https://github.com/weaveworks/scope/pull/1371) +- Don't show container labels on container images + [#1374](https://github.com/weaveworks/scope/pull/1374) +- `docker rm -f`ed containers linger + [#1072](https://github.com/weaveworks/scope/issues/1072) +- Somehow internet node goes missing, yet edges are there + [#1304](https://github.com/weaveworks/scope/pull/1304) +- Node IDs with / leads to redirect loop when scope is mounted under a path with slash redirect + [#1335](https://github.com/weaveworks/scope/issues/1335) +- Ignore conntracked connections on which we never saw an update + [#1466](https://github.com/weaveworks/scope/issues/466) +- Containers incorrectly attributed to host + [#1472](https://github.com/weaveworks/scope/issues/1472) +- k8s: Unexpected edge to the Internet node + [#1469](https://github.com/weaveworks/scope/issues/1469) +- When user supplies IP addr on command line, we don't try to connect to localhost + [#1477](https://github.com/weaveworks/scope/issues/1477) +- Wrong host labels on container nodes + [#1501](https://github.com/weaveworks/scope/issues/1501) + +Documentation: +- Restructured Scope Docs + [#1416](https://github.com/weaveworks/scope/pull/1416) + [#1479](https://github.com/weaveworks/scope/pull/1479) +- Add ECS instructions and badge to README + [#1392](https://github.com/weaveworks/scope/pull/1392) +- Document how to access the Scope UI in k8s + [#1426](https://github.com/weaveworks/scope/pull/1426) +- Update readme to express that daemon sets won't schedule on unschedulable nodes prior to kubernetes 1.2 + [#1434](https://github.com/weaveworks/scope/pull/1434) + + +Internal improvements and cleanup: +- Migrate from Flux to Redux + [#1388](https://github.com/weaveworks/scope/pull/1388) +- Add kubernetes checkpoint flag + [#1391](https://github.com/weaveworks/scope/pull/1391) +- Add generic path rewrite middleware + [#1381](https://github.com/weaveworks/scope/pull/1381) +- Report hostname and version in probe struct, and version in host node. + [#1377](https://github.com/weaveworks/scope/pull/1377) +- Reorganise the render/ package + [#1360](https://github.com/weaveworks/scope/pull/1360) +- Asset fingerprinting + [#1354](https://github.com/weaveworks/scope/pull/1354) +- Upgrade to go1.6.2 + [#1362](https://github.com/weaveworks/scope/pull/1362) +- Add buffer to mockPublisher channel to prevent deadlock between Publish() and Stop() + [#1358](https://github.com/weaveworks/scope/pull/1358) +- Add explicit group node summariser instead of doing it in the other summaries + [#1327](https://github.com/weaveworks/scope/pull/1327) +- Don't build codecs for render/ package anymore. + [#1345](https://github.com/weaveworks/scope/pull/1345) +- Measure report sizes + [#1458](https://github.com/weaveworks/scope/pull/1458) + ## Release 0.14.0 Highlights: diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index d5eaa6191..792d9b821 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -357,19 +357,21 @@ export function hitEsc() { return (dispatch, getState) => { const state = getState(); const controlPipe = state.get('controlPipes').last(); - if (state.get('showingHelp')) { - dispatch(hideHelp()); - } else if (state.get('searchQuery')) { - dispatch(doSearch('')); - } else if (state.get('searchFocused')) { - dispatch(blurSearch()); - } else if (controlPipe && controlPipe.get('status') === 'PIPE_DELETED') { + if (controlPipe && controlPipe.get('status') === 'PIPE_DELETED') { dispatch({ type: ActionTypes.CLICK_CLOSE_TERMINAL, pipeId: controlPipe.get('id') }); updateRoute(getState); // Don't deselect node on ESC if there is a controlPipe (keep terminal open) + } else if (state.get('searchFocused')) { + if (state.get('searchQuery')) { + dispatch(doSearch('')); + } else { + dispatch(blurSearch()); + } + } else if (state.get('showingHelp')) { + dispatch(hideHelp()); } else if (state.get('nodeDetails').last() && !controlPipe) { dispatch({ type: ActionTypes.DESELECT_NODE }); updateRoute(getState); diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index 32bb23cf7..11f27b0f0 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -54,6 +54,8 @@ class App extends React.Component { } onKeyUp(ev) { + const { showingTerminal } = this.props; + // don't get esc in onKeyPress if (ev.keyCode === ESC_KEY_CODE) { this.props.dispatch(hitEsc()); @@ -61,7 +63,7 @@ class App extends React.Component { this.props.dispatch(hitEnter()); } else if (ev.keyCode === BACKSPACE_KEY_CODE) { this.props.dispatch(hitBackspace()); - } else if (ev.code === 'KeyD' && ev.ctrlKey) { + } else if (ev.code === 'KeyD' && ev.ctrlKey && !showingTerminal) { toggleDebugToolbar(); this.forceUpdate(); } @@ -95,10 +97,7 @@ class App extends React.Component { } render() { - const { availableCanvasMetrics, nodeDetails, controlPipes, showingHelp } = this.props; - const showingDetails = nodeDetails.size > 0; - const showingTerminal = controlPipes.size > 0; - const showingMetricsSelector = availableCanvasMetrics.count() > 0; + const { showingDetails, showingHelp, showingMetricsSelector, showingTerminal } = this.props; return (
@@ -137,13 +136,13 @@ class App extends React.Component { function mapStateToProps(state) { return { activeTopologyOptions: getActiveTopologyOptions(state), - availableCanvasMetrics: state.get('availableCanvasMetrics'), - controlPipes: state.get('controlPipes'), - nodeDetails: state.get('nodeDetails'), routeSet: state.get('routeSet'), searchFocused: state.get('searchFocused'), searchQuery: state.get('searchQuery'), + showingDetails: state.get('nodeDetails').size > 0, showingHelp: state.get('showingHelp'), + showingMetricsSelector: state.get('availableCanvasMetrics').count() > 0, + showingTerminal: state.get('controlPipes').size > 0, urlState: getUrlState(state) }; } diff --git a/client/app/scripts/components/search.js b/client/app/scripts/components/search.js index 8b10b71f6..279aedff0 100644 --- a/client/app/scripts/components/search.js +++ b/client/app/scripts/components/search.js @@ -42,6 +42,7 @@ class Search extends React.Component { constructor(props, context) { super(props, context); + this.handleBlur = this.handleBlur.bind(this); this.handleChange = this.handleChange.bind(this); this.handleFocus = this.handleFocus.bind(this); this.doSearch = _.debounce(this.doSearch.bind(this), 200); @@ -50,6 +51,10 @@ class Search extends React.Component { }; } + handleBlur() { + this.props.blurSearch(); + } + handleChange(ev) { const inputValue = ev.target.value; let value = inputValue; @@ -119,7 +124,7 @@ class Search extends React.Component { .map(query => )}
{!showPinnedSearches &&
diff --git a/client/app/scripts/components/status.js b/client/app/scripts/components/status.js index b22e190f7..3d2aca51f 100644 --- a/client/app/scripts/components/status.js +++ b/client/app/scripts/components/status.js @@ -3,7 +3,8 @@ import { connect } from 'react-redux'; class Status extends React.Component { render() { - const {errorUrl, topologiesLoaded, topology, websocketClosed} = this.props; + const {errorUrl, filteredNodeCount, topologiesLoaded, topology, + websocketClosed} = this.props; let title = ''; let text = 'Trying to reconnect...'; @@ -23,9 +24,9 @@ class Status extends React.Component { showWarningIcon = true; } else if (topology) { const stats = topology.get('stats'); - text = `${stats.get('node_count')} nodes`; + text = `${stats.get('node_count') - filteredNodeCount} nodes`; if (stats.get('filtered_nodes')) { - text = `${text} (${stats.get('filtered_nodes')} filtered)`; + text = `${text} (${stats.get('filtered_nodes') + filteredNodeCount} filtered)`; } classNames += ' status-stats'; showWarningIcon = false; @@ -43,6 +44,7 @@ class Status extends React.Component { function mapStateToProps(state) { return { errorUrl: state.get('errorUrl'), + filteredNodeCount: state.get('nodes').filter(node => node.get('filtered')).size, topologiesLoaded: state.get('topologiesLoaded'), topology: state.get('currentTopology'), websocketClosed: state.get('websocketClosed') diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js index de3669641..244f09921 100644 --- a/client/app/scripts/reducers/root.js +++ b/client/app/scripts/reducers/root.js @@ -14,21 +14,6 @@ const error = debug('scope:error'); // Helpers -function makeNode(node) { - return { - id: node.id, - label: node.label, - label_minor: node.label_minor, - node_count: node.node_count, - rank: node.rank, - pseudo: node.pseudo, - stack: node.stack, - shape: node.shape, - adjacency: node.adjacency, - metrics: node.metrics - }; -} - const topologySorter = topology => topology.get('rank'); // Initial values @@ -490,7 +475,7 @@ export function rootReducer(state = initialState, action) { // add new nodes _.each(action.delta.add, (node) => { - state = state.setIn(['nodes', node.id], fromJS(makeNode(node))); + state = state.setIn(['nodes', node.id], fromJS(node)); }); // apply pinned searches, filters nodes that dont match diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js index f54a70357..38c915a7e 100644 --- a/client/app/scripts/utils/web-api-utils.js +++ b/client/app/scripts/utils/web-api-utils.js @@ -1,7 +1,7 @@ import debug from 'debug'; import reqwest from 'reqwest'; -import { clearControlError, closeWebsocket, openWebsocket, receiveError, +import { blurSearch, clearControlError, closeWebsocket, openWebsocket, receiveError, receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError, receiveControlNodeRemoved, receiveControlPipe, receiveControlPipeStatus, receiveControlSuccess, receiveTopologies, receiveNotFound, @@ -208,6 +208,7 @@ export function doControlRequest(nodeId, control, dispatch) { dispatch(receiveControlSuccess(nodeId)); if (res) { if (res.pipe) { + dispatch(blurSearch()); dispatch(receiveControlPipe(res.pipe, nodeId, res.raw_tty, true)); } if (res.removedNode) { diff --git a/integration/106_launch_with_peers_test.sh b/integration/106_launch_with_peers_test.sh new file mode 100755 index 000000000..d4a5e94da --- /dev/null +++ b/integration/106_launch_with_peers_test.sh @@ -0,0 +1,13 @@ +#! /bin/bash + +. ./config.sh + +start_suite "Launch scope and check it boots, with a spurious host arg" + +scope_on $HOST1 launch noatrealhost.foo + +wait_for_containers $HOST1 60 weavescope + +has_container $HOST1 weavescope + +scope_end_suite diff --git a/integration/205_clustering_sans_weave_2_test.sh b/integration/205_clustering_sans_weave_2_test.sh index abd498682..21d5921a6 100755 --- a/integration/205_clustering_sans_weave_2_test.sh +++ b/integration/205_clustering_sans_weave_2_test.sh @@ -4,8 +4,8 @@ start_suite "Launch 2 scopes and check they cluster (without weave)" -scope_on $HOST1 launch $HOST1 $HOST2 -scope_on $HOST2 launch $HOST1 $HOST2 +scope_on $HOST1 launch $HOST2 +scope_on $HOST2 launch $HOST1 docker_on $HOST1 run -dit --name db1 peterbourgon/tns-db docker_on $HOST2 run -dit --name db2 peterbourgon/tns-db diff --git a/integration/215_clustering_split_2_test.sh b/integration/215_clustering_split_2_test.sh new file mode 100755 index 000000000..7a399976d --- /dev/null +++ b/integration/215_clustering_split_2_test.sh @@ -0,0 +1,17 @@ +#! /bin/bash + +. ./config.sh + +start_suite "Launch 2 scopes and check they cluster (without weave)" + +scope_on $HOST1 launch --no-app $HOST2 +scope_on $HOST2 launch --no-probe + +docker_on $HOST1 run -dit --name db1 peterbourgon/tns-db + +sleep 30 # need to allow the scopes to poll dns, resolve the other app ids, and send them reports. + +has_container $HOST2 weavescope +has_container $HOST2 db1 + +scope_end_suite diff --git a/probe/kubernetes/reporter.go b/probe/kubernetes/reporter.go index 10de1ce03..4e33651ab 100644 --- a/probe/kubernetes/reporter.go +++ b/probe/kubernetes/reporter.go @@ -12,6 +12,7 @@ import ( "github.com/weaveworks/scope/probe" "github.com/weaveworks/scope/probe/controls" "github.com/weaveworks/scope/probe/docker" + "github.com/weaveworks/scope/probe/host" "github.com/weaveworks/scope/report" ) @@ -88,15 +89,17 @@ type Reporter struct { pipes controls.PipeClient probeID string probe *probe.Probe + hostID string } // NewReporter makes a new Reporter -func NewReporter(client Client, pipes controls.PipeClient, probeID string, probe *probe.Probe) *Reporter { +func NewReporter(client Client, pipes controls.PipeClient, probeID string, hostID string, probe *probe.Probe) *Reporter { reporter := &Reporter{ client: client, pipes: pipes, probeID: probeID, probe: probe, + hostID: hostID, } reporter.registerControls() client.WatchPods(reporter.podEvent) @@ -180,6 +183,10 @@ func (r *Reporter) Report() (report.Report, error) { if err != nil { return result, err } + hostTopology := r.hostTopology(services) + if err != nil { + return result, err + } deploymentTopology, deployments, err := r.deploymentTopology(r.probeID) if err != nil { return result, err @@ -194,6 +201,7 @@ func (r *Reporter) Report() (report.Report, error) { } result.Pod = result.Pod.Merge(podTopology) result.Service = result.Service.Merge(serviceTopology) + result.Host = result.Host.Merge(hostTopology) result.Deployment = result.Deployment.Merge(deploymentTopology) result.ReplicaSet = result.ReplicaSet.Merge(replicaSetTopology) return result, nil @@ -214,6 +222,25 @@ func (r *Reporter) serviceTopology() (report.Topology, []Service, error) { return result, services, err } +// FIXME: Hideous hack to remove persistent-connection edges to virtual service +// IPs attributed to the internet. We add each service IP as a /32 network +// (the global service-cluster-ip-range is not exposed by the API +// server so we treat each IP as a /32 network see +// https://github.com/kubernetes/kubernetes/issues/25533). +// The right way of fixing this is performing DNAT mapping on persistent +// connections for which we don't have a robust solution +// (see https://github.com/weaveworks/scope/issues/1491) +func (r *Reporter) hostTopology(services []Service) report.Topology { + localNetworks := report.EmptyStringSet + for _, service := range services { + localNetworks = localNetworks.Add(service.ClusterIP() + "/32") + } + node := report.MakeNode(report.MakeHostNodeID(r.hostID)) + node = node.WithSets(report.EmptySets. + Add(host.LocalNetworks, localNetworks)) + return report.MakeTopology().AddNode(node) +} + func (r *Reporter) deploymentTopology(probeID string) (report.Topology, []Deployment, error) { var ( result = report.MakeTopology(). diff --git a/probe/kubernetes/reporter_test.go b/probe/kubernetes/reporter_test.go index d9e10f8ee..b5810b57d 100644 --- a/probe/kubernetes/reporter_test.go +++ b/probe/kubernetes/reporter_test.go @@ -184,7 +184,7 @@ func TestReporter(t *testing.T) { pod1ID := report.MakePodNodeID(pod1UID) pod2ID := report.MakePodNodeID(pod2UID) serviceID := report.MakeServiceNodeID(serviceUID) - rpt, _ := kubernetes.NewReporter(newMockClient(), nil, "", nil).Report() + rpt, _ := kubernetes.NewReporter(newMockClient(), nil, "", "foo", nil).Report() // Reporter should have added the following pods for _, pod := range []struct { @@ -247,7 +247,7 @@ func TestTagger(t *testing.T) { docker.LabelPrefix + "io.kubernetes.pod.uid": "123456", })) - rpt, err := kubernetes.NewReporter(newMockClient(), nil, "", nil).Tag(rpt) + rpt, err := kubernetes.NewReporter(newMockClient(), nil, "", "", nil).Tag(rpt) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -275,7 +275,7 @@ func TestReporterGetLogs(t *testing.T) { client := newMockClient() pipes := mockPipeClient{} - reporter := kubernetes.NewReporter(client, pipes, "", nil) + reporter := kubernetes.NewReporter(client, pipes, "", "", nil) // Should error on invalid IDs { diff --git a/probe/kubernetes/service.go b/probe/kubernetes/service.go index e758d4248..318385276 100644 --- a/probe/kubernetes/service.go +++ b/probe/kubernetes/service.go @@ -16,6 +16,7 @@ type Service interface { Meta GetNode() report.Node Selector() labels.Selector + ClusterIP() string } type service struct { @@ -42,3 +43,7 @@ func (s *service) GetNode() report.Node { } return s.MetaNode(report.MakeServiceNodeID(s.UID())).WithLatests(latest) } + +func (s *service) ClusterIP() string { + return s.Spec.ClusterIP +} diff --git a/prog/main.go b/prog/main.go index 0f701109b..72d999f3e 100644 --- a/prog/main.go +++ b/prog/main.go @@ -68,6 +68,7 @@ type probeFlags struct { logPrefix string logLevel string resolver string + noApp bool dockerEnabled bool dockerInterval time.Duration @@ -115,12 +116,13 @@ func main() { flag.BoolVar(&debug, "debug", false, "Force debug logging.") flag.StringVar(&weaveHostname, "weave.hostname", "", "Hostname to advertise/lookup in WeaveDNS") - // We can ignore these - we need to know how to parse them, but they are interpreted by the entrypoint script. - // They are also here so they are included in usage. - flag.Bool("no-app", false, "Don't run the app.") + // We need to know how to parse them, but they are mainly interpreted by the entrypoint script. + // They are also here so they are included in usage, and the probe uses them to decide if to + // publish to localhost. + noApp := flag.Bool("no-app", false, "Don't run the app.") + probeOnly := flag.Bool("app-only", false, "Only run the app") flag.Bool("probe-only", false, "Only run the probe.") flag.Bool("no-probe", false, "Don't run the probe.") - flag.Bool("app-only", false, "Only run the app") // Probe flags flag.StringVar(&flags.probe.token, "service-token", "", "Token to use to authenticate with scope.weave.works") @@ -176,6 +178,7 @@ func main() { flags.probe.weaveHostname = weaveHostname flags.app.weaveHostname = weaveHostname } + flags.probe.noApp = *noApp || *probeOnly switch mode { case "app": diff --git a/prog/probe.go b/prog/probe.go index b0c2673bc..e29522cb0 100644 --- a/prog/probe.go +++ b/prog/probe.go @@ -94,9 +94,12 @@ func probeMain(flags probeFlags) { } go check(checkpointFlags) - var targets = []string{fmt.Sprintf("localhost:%d", xfer.AppPort)} + var targets = []string{} + if !flags.noApp { + targets = append(targets, fmt.Sprintf("localhost:%d", xfer.AppPort)) + } if len(flag.Args()) > 0 { - targets = flag.Args() + targets = append(targets, flag.Args()...) } log.Infof("publishing to: %s", strings.Join(targets, ", ")) @@ -157,7 +160,7 @@ func probeMain(flags probeFlags) { if flags.kubernetesEnabled { if client, err := kubernetes.NewClient(flags.kubernetesAPI, flags.kubernetesInterval); err == nil { defer client.Stop() - reporter := kubernetes.NewReporter(client, clients, probeID, p) + reporter := kubernetes.NewReporter(client, clients, probeID, hostID, p) defer reporter.Stop() p.AddReporter(reporter) p.AddTagger(reporter) diff --git a/render/container.go b/render/container.go index 2cc8b1d1c..75a0e006b 100644 --- a/render/container.go +++ b/render/container.go @@ -62,9 +62,8 @@ type containerWithImageNameRenderer struct { Renderer } -// Render produces a process graph where the minor labels contain the -// container name, if found. It also merges the image node metadata into the -// container metadata. +// Render produces a container graph where the the latest metadata contains the +// container image name, if found. func (r containerWithImageNameRenderer) Render(rpt report.Report, dct Decorator) report.Nodes { containers := r.Renderer.Render(rpt, dct) images := SelectContainerImage.Render(rpt, dct) @@ -81,7 +80,8 @@ func (r containerWithImageNameRenderer) Render(rpt report.Report, dct Decorator) continue } output := c.Copy() - output.Latest = image.Latest.Merge(c.Latest) + output = propagateLatest(docker.ImageName, image, output) + output = propagateLatest(docker.ImageLabelPrefix+"works.weave.role", image, output) outputs[id] = output } return outputs diff --git a/render/host.go b/render/host.go index 6c7103d74..f56c28035 100644 --- a/render/host.go +++ b/render/host.go @@ -52,6 +52,7 @@ func MapX2Host(n report.Node, _ report.Networks) report.Nodes { result := NewDerivedNode(id, n).WithTopology(report.Host) result.Latest = result.Latest.Set(report.HostNodeID, timestamp, hostNodeID) result.Counters = result.Counters.Add(n.Topology, 1) + result.Children = report.MakeNodeSet(n) return report.Nodes{id: result} } diff --git a/scope b/scope index 1c9649d59..ff634330b 100755 --- a/scope +++ b/scope @@ -69,11 +69,11 @@ check_probe_only() { } check_docker_for_mac() { - docker_for_mac_network_mode="${HOME}/Library/Containers/com.docker.docker/Data/database/com.docker.driver.amd64-linux/network" [ "$(uname)" = "Darwin" ] \ && [ -S /var/run/docker.sock ] \ && [ ! "${DOCKER_HOST+x}" = x ] \ - && grep -q hybrid $docker_for_mac_network_mode 2>/dev/null + && [ ! "${HOME+x}" = x ] \ + && grep -q hybrid "${HOME}/Library/Containers/com.docker.docker/Data/database/com.docker.driver.amd64-linux/network" 2>/dev/null } # Check that a container named $1 with image $2 is not running