ctop/connector/docker.go

223 lines
5.1 KiB
Go
Raw Normal View History

package connector
2016-12-30 22:17:46 +00:00
import (
"fmt"
2017-03-03 07:57:26 +00:00
"strings"
"sync"
2017-01-12 19:24:12 +00:00
2017-06-12 14:12:03 +00:00
"github.com/bcicen/ctop/connector/collector"
2017-11-22 14:27:38 +00:00
"github.com/bcicen/ctop/connector/manager"
"github.com/bcicen/ctop/container"
2017-06-08 15:01:08 +00:00
api "github.com/fsouza/go-dockerclient"
2016-12-30 22:17:46 +00:00
)
2018-01-29 12:47:10 +00:00
func init() { enabled["docker"] = NewDocker }
2017-06-08 15:01:08 +00:00
type Docker struct {
client *api.Client
containers map[string]*container.Container
needsRefresh chan string // container IDs requiring refresh
closed chan struct{}
lock sync.RWMutex
}
func NewDocker() (Connector, error) {
// init docker client
2017-06-08 15:01:08 +00:00
client, err := api.NewClientFromEnv()
if err != nil {
return nil, err
}
2017-06-08 15:01:08 +00:00
cm := &Docker{
client: client,
containers: make(map[string]*container.Container),
needsRefresh: make(chan string, 60),
closed: make(chan struct{}),
lock: sync.RWMutex{},
2016-12-30 22:17:46 +00:00
}
// query info as pre-flight healthcheck
info, err := client.Info()
if err != nil {
return nil, err
}
log.Debugf("docker-connector ID: %s", info.ID)
log.Debugf("docker-connector Driver: %s", info.Driver)
log.Debugf("docker-connector Images: %d", info.Images)
log.Debugf("docker-connector Name: %s", info.Name)
log.Debugf("docker-connector ServerVersion: %s", info.ServerVersion)
go cm.Loop()
cm.refreshAll()
2017-02-24 09:10:14 +00:00
go cm.watchEvents()
return cm, nil
2016-12-30 22:17:46 +00:00
}
// Docker implements Connector
func (cm *Docker) Wait() struct{} { return <-cm.closed }
// Docker events watcher
2017-06-08 15:01:08 +00:00
func (cm *Docker) watchEvents() {
log.Info("docker event listener starting")
2017-06-08 15:01:08 +00:00
events := make(chan *api.APIEvents)
cm.client.AddEventListener(events)
for e := range events {
2017-02-24 09:10:14 +00:00
if e.Type != "container" {
continue
}
actionName := strings.Split(e.Action, ":")[0]
switch actionName {
case "start", "die", "pause", "unpause", "health_status":
2017-02-24 09:10:14 +00:00
log.Debugf("handling docker event: action=%s id=%s", e.Action, e.ID)
cm.needsRefresh <- e.ID
2017-02-24 09:10:14 +00:00
case "destroy":
log.Debugf("handling docker event: action=%s id=%s", e.Action, e.ID)
cm.delByID(e.ID)
2017-02-24 09:10:14 +00:00
}
}
log.Info("docker event listener exited")
close(cm.closed)
}
2017-06-08 15:01:08 +00:00
func portsFormat(ports map[api.Port][]api.PortBinding) string {
var exposed []string
var published []string
for k, v := range ports {
if len(v) == 0 {
exposed = append(exposed, string(k))
continue
}
for _, binding := range v {
s := fmt.Sprintf("%s:%s -> %s", binding.HostIP, binding.HostPort, k)
published = append(published, s)
}
}
return strings.Join(append(exposed, published...), "\n")
}
2018-09-06 19:01:16 +00:00
func ipsFormat(networks map[string]api.ContainerNetwork) string {
var ips []string
for k, v := range networks {
s := fmt.Sprintf("%s:%s", k, v.IPAddress)
ips = append(ips, s)
}
return strings.Join(ips, "\n")
}
2017-06-08 15:01:08 +00:00
func (cm *Docker) refresh(c *container.Container) {
insp := cm.inspect(c.Id)
// remove container if no longer exists
if insp == nil {
cm.delByID(c.Id)
return
}
c.SetMeta("name", shortName(insp.Name))
c.SetMeta("image", insp.Config.Image)
2018-09-06 19:01:16 +00:00
c.SetMeta("IPs", ipsFormat(insp.NetworkSettings.Networks))
c.SetMeta("ports", portsFormat(insp.NetworkSettings.Ports))
c.SetMeta("created", insp.Created.Format("Mon Jan 2 15:04:05 2006"))
c.SetMeta("health", insp.State.Health.Status)
2018-10-05 21:21:24 +00:00
for _, env := range insp.Config.Env {
2018-10-25 20:22:28 +00:00
c.SetMeta("[ENV-VAR]", env)
2018-10-05 21:21:24 +00:00
}
c.SetState(insp.State.Status)
}
2017-06-08 15:01:08 +00:00
func (cm *Docker) inspect(id string) *api.Container {
c, err := cm.client.InspectContainer(id)
if err != nil {
2018-10-25 20:12:17 +00:00
if _, ok := err.(*api.NoSuchContainer); !ok {
log.Errorf("%s (%T)", err.Error(), err)
}
}
return c
}
// Mark all container IDs for refresh
2017-06-08 15:01:08 +00:00
func (cm *Docker) refreshAll() {
opts := api.ListContainersOptions{All: true}
allContainers, err := cm.client.ListContainers(opts)
if err != nil {
log.Errorf("%s (%T)", err.Error(), err)
return
}
for _, i := range allContainers {
c := cm.MustGet(i.ID)
c.SetMeta("name", shortName(i.Names[0]))
c.SetState(i.State)
cm.needsRefresh <- c.Id
}
}
2017-06-08 15:01:08 +00:00
func (cm *Docker) Loop() {
for {
select {
case id := <-cm.needsRefresh:
c := cm.MustGet(id)
cm.refresh(c)
case <-cm.closed:
return
}
}
}
// MustGet gets a single container, creating one anew if not existing
2017-06-08 15:01:08 +00:00
func (cm *Docker) MustGet(id string) *container.Container {
c, ok := cm.Get(id)
// append container struct for new containers
if !ok {
// create collector
2017-06-12 14:12:03 +00:00
collector := collector.NewDocker(cm.client, id)
2017-11-20 11:09:36 +00:00
// create manager
manager := manager.NewDocker(cm.client, id)
// create container
2017-11-20 11:09:36 +00:00
c = container.New(id, collector, manager)
cm.lock.Lock()
cm.containers[id] = c
cm.lock.Unlock()
}
return c
}
// Docker implements Connector
2017-06-08 15:01:08 +00:00
func (cm *Docker) Get(id string) (*container.Container, bool) {
cm.lock.Lock()
c, ok := cm.containers[id]
cm.lock.Unlock()
return c, ok
2016-12-30 22:17:46 +00:00
}
// Remove containers by ID
2017-06-08 15:01:08 +00:00
func (cm *Docker) delByID(id string) {
cm.lock.Lock()
delete(cm.containers, id)
cm.lock.Unlock()
log.Infof("removed dead container: %s", id)
}
// Docker implements Connector
2017-06-08 15:01:08 +00:00
func (cm *Docker) All() (containers container.Containers) {
cm.lock.Lock()
for _, c := range cm.containers {
containers = append(containers, c)
}
2017-06-12 13:40:52 +00:00
containers.Sort()
2017-03-08 00:26:22 +00:00
containers.Filter()
cm.lock.Unlock()
return containers
2017-03-03 07:57:26 +00:00
}
// use primary container name
func shortName(name string) string {
return strings.Replace(name, "/", "", 1)
}