Merge pull request #1290 from weaveworks/remove-container-control

Add control for removing stopped docker containers.
This commit is contained in:
Paul Bellamy
2016-04-22 14:13:40 +01:00
14 changed files with 98 additions and 35 deletions
@@ -322,6 +322,14 @@ export function receiveApiDetails(apiDetails) {
});
}
export function receiveControlNodeRemoved(nodeId) {
AppDispatcher.dispatch({
type: ActionTypes.RECEIVE_CONTROL_NODE_REMOVED,
nodeId
});
updateRoute();
}
export function receiveControlPipeFromParams(pipeId, rawTty) {
// TODO add nodeId
AppDispatcher.dispatch({
@@ -1,4 +1,5 @@
import React from 'react';
import _ from 'lodash';
import NodeDetailsControlButton from './node-details-control-button';
@@ -17,7 +18,7 @@ export default function NodeDetailsControls({controls, error, nodeId, pending})
<span className="node-details-controls-error-messages">{error}</span>
</div>}
<span className="node-details-controls-buttons">
{controls && controls.map(control => <NodeDetailsControlButton
{_.sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
nodeId={nodeId} control={control} pending={pending} key={control.id} />)}
</span>
{controls && <span title="Applying..." className={spinnerClassName}></span>}
@@ -27,6 +27,7 @@ const ACTION_TYPES = [
'PIN_METRIC',
'UNPIN_METRIC',
'OPEN_WEBSOCKET',
'RECEIVE_CONTROL_NODE_REMOVED',
'RECEIVE_CONTROL_PIPE',
'RECEIVE_CONTROL_PIPE_STATUS',
'RECEIVE_NODE_DETAILS',
+5
View File
@@ -561,6 +561,11 @@ export class AppStore extends Store {
this.__emitChange();
break;
}
case ActionTypes.RECEIVE_CONTROL_NODE_REMOVED: {
closeNodeDetails(payload.nodeId);
this.__emitChange();
break;
}
case ActionTypes.RECEIVE_CONTROL_PIPE: {
controlPipes = controlPipes.set(payload.pipeId, makeOrderedMap({
id: payload.pipeId,
+9 -4
View File
@@ -3,8 +3,8 @@ import reqwest from 'reqwest';
import { clearControlError, closeWebsocket, openWebsocket, receiveError,
receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError,
receiveControlPipe, receiveControlPipeStatus, receiveControlSuccess,
receiveTopologies, receiveNotFound } from '../actions/app-actions';
receiveControlNodeRemoved, receiveControlPipe, receiveControlPipeStatus,
receiveControlSuccess, receiveTopologies, receiveNotFound } from '../actions/app-actions';
import { API_INTERVAL, TOPOLOGY_INTERVAL } from '../constants/timer';
@@ -184,8 +184,13 @@ export function doControlRequest(nodeId, control) {
url,
success: (res) => {
receiveControlSuccess(nodeId);
if (res && res.pipe) {
receiveControlPipe(res.pipe, nodeId, res.raw_tty, true);
if (res) {
if (res.pipe) {
receiveControlPipe(res.pipe, nodeId, res.raw_tty, true);
}
if (res.removedNode) {
receiveControlNodeRemoved(nodeId);
}
}
},
error: (err) => {
+9 -4
View File
@@ -18,10 +18,15 @@ type Request struct {
// Response is the Probe -> App -> UI message type for the control RPCs.
type Response struct {
Value interface{} `json:"value,omitempty"`
Error string `json:"error,omitempty"`
Pipe string `json:"pipe,omitempty"`
RawTTY bool `json:"raw_tty,omitempty"`
Value interface{} `json:"value,omitempty"`
Error string `json:"error,omitempty"`
// Pipe specific fields
Pipe string `json:"pipe,omitempty"`
RawTTY bool `json:"raw_tty,omitempty"`
// Remove specific fields
RemovedNode string `json:"removedNode,omitempty"` // Set if node was removed
}
// Message is the unions of Request, Response and arbitrary Value.
+1 -1
View File
@@ -394,7 +394,7 @@ func (c *container) GetNode(localAddrs []net.IP) report.Node {
RestartContainer, StopContainer, PauseContainer, AttachContainer, ExecContainer,
)
} else {
result = result.WithControls(StartContainer)
result = result.WithControls(StartContainer, RemoveContainer)
}
result = result.AddTable(LabelPrefix, c.container.Config.Labels)
+15
View File
@@ -17,6 +17,7 @@ const (
RestartContainer = "docker_restart_container"
PauseContainer = "docker_pause_container"
UnpauseContainer = "docker_unpause_container"
RemoveContainer = "docker_remove_container"
AttachContainer = "docker_attach_container"
ExecContainer = "docker_exec_container"
@@ -48,6 +49,18 @@ func (r *registry) unpauseContainer(containerID string, _ xfer.Request) xfer.Res
return xfer.ResponseError(r.client.UnpauseContainer(containerID))
}
func (r *registry) removeContainer(containerID string, _ xfer.Request) xfer.Response {
log.Infof("Removing container %s", containerID)
if err := r.client.RemoveContainer(docker_client.RemoveContainerOptions{
ID: containerID,
}); err != nil {
return xfer.ResponseError(err)
}
return xfer.Response{
RemovedNode: containerID,
}
}
func (r *registry) attachContainer(containerID string, req xfer.Request) xfer.Response {
c, ok := r.GetContainer(containerID)
if !ok {
@@ -156,6 +169,7 @@ func (r *registry) registerControls() {
controls.Register(RestartContainer, captureContainerID(r.restartContainer))
controls.Register(PauseContainer, captureContainerID(r.pauseContainer))
controls.Register(UnpauseContainer, captureContainerID(r.unpauseContainer))
controls.Register(RemoveContainer, captureContainerID(r.removeContainer))
controls.Register(AttachContainer, captureContainerID(r.attachContainer))
controls.Register(ExecContainer, captureContainerID(r.execContainer))
}
@@ -166,6 +180,7 @@ func (r *registry) deregisterControls() {
controls.Rm(RestartContainer)
controls.Rm(PauseContainer)
controls.Rm(UnpauseContainer)
controls.Rm(RemoveContainer)
controls.Rm(AttachContainer)
controls.Rm(ExecContainer)
}
+1
View File
@@ -70,6 +70,7 @@ type Client interface {
RestartContainer(string, uint) error
PauseContainer(string) error
UnpauseContainer(string) error
RemoveContainer(docker_client.RemoveContainerOptions) error
AttachToContainerNonBlocking(docker_client.AttachToContainerOptions) (docker_client.CloseWaiter, error)
CreateExec(docker_client.CreateExecOptions) (*docker_client.Exec, error)
StartExecNonBlocking(string, docker_client.StartExecOptions) (docker_client.CloseWaiter, error)
+4
View File
@@ -138,6 +138,10 @@ func (m *mockDockerClient) UnpauseContainer(_ string) error {
return fmt.Errorf("unpaused")
}
func (m *mockDockerClient) RemoveContainer(_ client.RemoveContainerOptions) error {
return fmt.Errorf("remove")
}
type mockCloseWaiter struct{}
func (mockCloseWaiter) Close() error { return nil }
+38 -25
View File
@@ -100,40 +100,53 @@ func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology {
WithMetadataTemplates(ContainerMetadataTemplates).
WithMetricTemplates(ContainerMetricTemplates).
WithTableTemplates(ContainerTableTemplates)
result.Controls.AddControl(report.Control{
ID: StopContainer,
Human: "Stop",
Icon: "fa-stop",
})
result.Controls.AddControl(report.Control{
ID: StartContainer,
Human: "Start",
Icon: "fa-play",
})
result.Controls.AddControl(report.Control{
ID: RestartContainer,
Human: "Restart",
Icon: "fa-repeat",
})
result.Controls.AddControl(report.Control{
ID: PauseContainer,
Human: "Pause",
Icon: "fa-pause",
})
result.Controls.AddControl(report.Control{
ID: UnpauseContainer,
Human: "Unpause",
Icon: "fa-play",
})
result.Controls.AddControl(report.Control{
ID: AttachContainer,
Human: "Attach",
Icon: "fa-desktop",
Rank: 1,
})
result.Controls.AddControl(report.Control{
ID: ExecContainer,
Human: "Exec shell",
Icon: "fa-terminal",
Rank: 2,
})
result.Controls.AddControl(report.Control{
ID: StartContainer,
Human: "Start",
Icon: "fa-play",
Rank: 3,
})
result.Controls.AddControl(report.Control{
ID: RestartContainer,
Human: "Restart",
Icon: "fa-repeat",
Rank: 4,
})
result.Controls.AddControl(report.Control{
ID: PauseContainer,
Human: "Pause",
Icon: "fa-pause",
Rank: 5,
})
result.Controls.AddControl(report.Control{
ID: UnpauseContainer,
Human: "Unpause",
Icon: "fa-play",
Rank: 6,
})
result.Controls.AddControl(report.Control{
ID: StopContainer,
Human: "Stop",
Icon: "fa-stop",
Rank: 7,
})
result.Controls.AddControl(report.Control{
ID: RemoveContainer,
Human: "Remove",
Icon: "fa-trash-o",
Rank: 8,
})
metadata := map[string]string{report.ControlProbeID: r.probeID}
+1
View File
@@ -129,6 +129,7 @@ func (r *Reporter) podTopology(services []Service) (report.Topology, report.Topo
ID: GetLogs,
Human: "Get logs",
Icon: "fa-desktop",
Rank: 0,
})
for _, service := range services {
selectors[service.ID()] = service.Selector()
+3
View File
@@ -45,6 +45,7 @@ type wiredControlInstance struct {
ID string `json:"id"`
Human string `json:"human"`
Icon string `json:"icon"`
Rank int `json:"rank"`
}
// CodecEncodeSelf marshals this ControlInstance. It takes the basic Metric
@@ -56,6 +57,7 @@ func (c *ControlInstance) CodecEncodeSelf(encoder *codec.Encoder) {
ID: c.Control.ID,
Human: c.Control.Human,
Icon: c.Control.Icon,
Rank: c.Control.Rank,
})
}
@@ -70,6 +72,7 @@ func (c *ControlInstance) CodecDecodeSelf(decoder *codec.Decoder) {
ID: in.ID,
Human: in.Human,
Icon: in.Icon,
Rank: in.Rank,
},
}
}
+1
View File
@@ -16,6 +16,7 @@ type Control struct {
ID string `json:"id"`
Human string `json:"human"`
Icon string `json:"icon"` // from https://fortawesome.github.io/Font-Awesome/cheatsheet/ please
Rank int `json:"rank"`
}
// Merge merges other with cs, returning a fresh Controls.