diff --git a/app/api_topologies.go b/app/api_topologies.go index 6930aeaf4e..715cafee22 100644 --- a/app/api_topologies.go +++ b/app/api_topologies.go @@ -24,33 +24,69 @@ var ( renderer: render.PodRenderer, Name: "Pods", Rank: 3, - Options: map[string][]APITopologyOption{"system": { - {"show", "System pods shown", false, render.FilterNoop}, - {"hide", "System pods hidden", true, render.FilterSystem}, - }}, + Options: []APITopologyOptionGroup{ + { + ID: "system", + Default: "application", + Options: []APITopologyOption{ + {"system", "System pods", render.FilterApplication}, + {"application", "Application pods", render.FilterSystem}, + {"both", "Both", render.FilterNoop}, + }, + }, + }, }, { id: "pods-by-service", parent: "pods", renderer: render.PodServiceRenderer, Name: "by service", - Options: map[string][]APITopologyOption{"system": { - {"show", "System services shown", false, render.FilterNoop}, - {"hide", "System services hidden", true, render.FilterSystem}, - }}, + Options: []APITopologyOptionGroup{ + { + ID: "system", + Default: "application", + Options: []APITopologyOption{ + {"system", "System services", render.FilterApplication}, + {"application", "Application services", render.FilterSystem}, + {"both", "Both", render.FilterNoop}, + }, + }, + }, }, } ) func init() { - containerFilters := map[string][]APITopologyOption{ - "system": { - {"show", "System containers shown", false, render.FilterNoop}, - {"hide", "System containers hidden", true, render.FilterSystem}, + containerFilters := []APITopologyOptionGroup{ + { + ID: "system", + Default: "application", + Options: []APITopologyOption{ + {"system", "System containers", render.FilterApplication}, + {"application", "Application containers", render.FilterSystem}, + {"both", "Both", render.FilterNoop}, + }, + }, + { + ID: "stopped", + Default: "running", + Options: []APITopologyOption{ + {"stopped", "Stopped containers", render.FilterRunning}, + {"running", "Running containers", render.FilterStopped}, + {"both", "Both", render.FilterNoop}, + }, }, - "stopped": { - {"show", "Stopped containers shown", false, render.FilterNoop}, - {"hide", "Stopped containers hidden", true, render.FilterStopped}, + } + + unconnectedFilter := []APITopologyOptionGroup{ + { + ID: "unconnected", + Default: "hide", + Options: []APITopologyOption{ + // Show the user why there are filtered nodes in this view. + // Don't give them the option to show those nodes. + {"hide", "Unconnected nodes hidden", render.FilterNoop}, + }, }, } @@ -62,21 +98,14 @@ func init() { renderer: render.FilterUnconnected(render.ProcessWithContainerNameRenderer), Name: "Processes", Rank: 1, - Options: map[string][]APITopologyOption{"unconnected": { - // Show the user why there are filtered nodes in this view. - // Don't give them the option to show those nodes. - {"hide", "Unconnected nodes hidden", true, render.FilterNoop}, - }}, + Options: unconnectedFilter, }, APITopologyDesc{ id: "processes-by-name", parent: "processes", renderer: render.FilterUnconnected(render.ProcessNameRenderer), Name: "by name", - Options: map[string][]APITopologyOption{"unconnected": { - // Ditto above. - {"hide", "Unconnected nodes hidden", true, render.FilterNoop}, - }}, + Options: unconnectedFilter, }, APITopologyDesc{ id: "containers", @@ -104,7 +133,6 @@ func init() { renderer: render.HostRenderer, Name: "Hosts", Rank: 4, - Options: map[string][]APITopologyOption{}, }, ) } @@ -121,9 +149,9 @@ type APITopologyDesc struct { parent string renderer render.Renderer - Name string `json:"name"` - Rank int `json:"rank"` - Options map[string][]APITopologyOption `json:"options"` + Name string `json:"name"` + Rank int `json:"rank"` + Options []APITopologyOptionGroup `json:"options"` URL string `json:"url"` SubTopologies []APITopologyDesc `json:"sub_topologies,omitempty"` @@ -136,11 +164,16 @@ func (a byName) Len() int { return len(a) } func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } +type APITopologyOptionGroup struct { + ID string `json:"id"` + Default string `json:"defaultValue,omitempty"` + Options []APITopologyOption `json:"options,omitempty"` +} + // APITopologyOption describes a ¶m=value to a given topology. type APITopologyOption struct { - Value string `json:"value"` - Display string `json:"display"` - Default bool `json:"default,omitempty"` + Value string `json:"value"` + Label string `json:"label"` decorator func(render.Renderer) render.Renderer } @@ -247,10 +280,10 @@ func (r *registry) enableKubernetesTopologies() { func renderedForRequest(r *http.Request, topology APITopologyDesc) render.Renderer { renderer := topology.renderer - for param, opts := range topology.Options { - value := r.FormValue(param) - for _, opt := range opts { - if (value == "" && opt.Default) || (opt.Value != "" && opt.Value == value) { + for _, group := range topology.Options { + value := r.FormValue(group.ID) + for _, opt := range group.Options { + if (value == "" && group.Default == opt.Value) || (opt.Value != "" && opt.Value == value) { renderer = opt.decorator(renderer) } } diff --git a/render/filters.go b/render/filters.go index 1f9f785cad..da27548bbe 100644 --- a/render/filters.go +++ b/render/filters.go @@ -1,10 +1,6 @@ package render import ( - "strings" - - "github.com/weaveworks/scope/probe/docker" - "github.com/weaveworks/scope/probe/kubernetes" "github.com/weaveworks/scope/report" ) @@ -124,6 +120,12 @@ func (f Filter) Stats(rpt report.Report) Stats { // to indicate a node has an edge pointing to it or from it const IsConnected = "is_connected" +// Complement takes a FilterFunc f and returns a FilterFunc that has the same +// effects, if any, and returns the opposite truth value. +func Complement(f func(RenderableNode) bool) func(RenderableNode) bool { + return func(node RenderableNode) bool { return !f(node) } +} + // FilterPseudo produces a renderer that removes pseudo nodes from the given // renderer func FilterPseudo(r Renderer) Renderer { @@ -154,44 +156,22 @@ func FilterNoop(in Renderer) Renderer { // FilterStopped filters out stopped containers. func FilterStopped(r Renderer) Renderer { - return MakeFilter( - func(node RenderableNode) bool { - containerState, ok := node.Latest.Lookup(docker.ContainerState) - return !ok || containerState != docker.StateStopped - }, - r, - ) + return MakeFilter(RenderableNode.IsStopped, r) +} + +// FilterRunning filters out running containers. +func FilterRunning(r Renderer) Renderer { + return MakeFilter(Complement(RenderableNode.IsStopped), r) } // FilterSystem is a Renderer which filters out system nodes. func FilterSystem(r Renderer) Renderer { - return MakeFilter( - func(node RenderableNode) bool { - containerName, _ := node.Latest.Lookup(docker.ContainerName) - if _, ok := systemContainerNames[containerName]; ok { - return false - } - imageName, _ := node.Latest.Lookup(docker.ImageName) - imagePrefix := strings.SplitN(imageName, ":", 2)[0] // :( - if _, ok := systemImagePrefixes[imagePrefix]; ok { - return false - } - roleLabel, _ := node.Latest.Lookup(docker.LabelPrefix + "works.weave.role") - if roleLabel == "system" { - return false - } - namespace, _ := node.Latest.Lookup(kubernetes.Namespace) - if namespace == "kube-system" { - return false - } - podName, _ := node.Latest.Lookup(docker.LabelPrefix + "io.kubernetes.pod.name") - if strings.HasPrefix(podName, "kube-system/") { - return false - } - return true - }, - r, - ) + return MakeFilter(RenderableNode.IsSystem, r) +} + +// FilterApplication is a Renderer which filters out system nodes. +func FilterApplication(r Renderer) Renderer { + return MakeFilter(Complement(RenderableNode.IsSystem), r) } var systemContainerNames = map[string]struct{}{ diff --git a/render/renderable_node.go b/render/renderable_node.go index 847057e000..97af4e136b 100644 --- a/render/renderable_node.go +++ b/render/renderable_node.go @@ -1,6 +1,10 @@ package render import ( + "strings" + + "github.com/weaveworks/scope/probe/docker" + "github.com/weaveworks/scope/probe/kubernetes" "github.com/weaveworks/scope/report" ) @@ -153,6 +157,38 @@ func (rn RenderableNode) Copy() RenderableNode { } } +// IsStopped checks if the RenderableNode is a stopped docker container. +func (rn RenderableNode) IsStopped() bool { + containerState, ok := rn.Latest.Lookup(docker.ContainerState) + return !ok || containerState != docker.StateStopped +} + +// IsSystem checks if the RenderableNode is a system container/pod/etc. +func (rn RenderableNode) IsSystem() bool { + containerName, _ := rn.Latest.Lookup(docker.ContainerName) + if _, ok := systemContainerNames[containerName]; ok { + return false + } + imageName, _ := rn.Latest.Lookup(docker.ImageName) + imagePrefix := strings.SplitN(imageName, ":", 2)[0] // :( + if _, ok := systemImagePrefixes[imagePrefix]; ok { + return false + } + roleLabel, _ := rn.Latest.Lookup(docker.LabelPrefix + "works.weave.role") + if roleLabel == "system" { + return false + } + namespace, _ := rn.Latest.Lookup(kubernetes.Namespace) + if namespace == "kube-system" { + return false + } + podName, _ := rn.Latest.Lookup(docker.LabelPrefix + "io.kubernetes.pod.name") + if strings.HasPrefix(podName, "kube-system/") { + return false + } + return true +} + // Prune returns a copy of the RenderableNode with all information not // strictly necessary for rendering nodes and edges stripped away. // Specifically, that means cutting out parts of the Node.