github.com/greenboxal/deis@v1.12.1/logspout/attacher.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"io"
     6  	"log"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/fsouza/go-dockerclient"
    11  )
    12  
    13  type AttachManager struct {
    14  	sync.Mutex
    15  	attached map[string]*LogPump
    16  	channels map[chan *AttachEvent]struct{}
    17  	client   *docker.Client
    18  }
    19  
    20  func NewAttachManager(client *docker.Client) *AttachManager {
    21  	m := &AttachManager{
    22  		attached: make(map[string]*LogPump),
    23  		channels: make(map[chan *AttachEvent]struct{}),
    24  		client:   client,
    25  	}
    26  	containers, err := client.ListContainers(docker.ListContainersOptions{})
    27  	assert(err, "attacher")
    28  	for _, listing := range containers {
    29  		m.attach(listing.ID[:12])
    30  	}
    31  	go func() {
    32  		events := make(chan *docker.APIEvents)
    33  		assert(client.AddEventListener(events), "attacher")
    34  		for msg := range events {
    35  			debug("event:", msg.ID[:12], msg.Status)
    36  			if msg.Status == "start" {
    37  				go m.attach(msg.ID[:12])
    38  			}
    39  		}
    40  		log.Fatal("ruh roh") // todo: loop?
    41  	}()
    42  	return m
    43  }
    44  
    45  func (m *AttachManager) attach(id string) {
    46  	container, err := m.client.InspectContainer(id)
    47  	assert(err, "attacher")
    48  	name := container.Name[1:]
    49  	success := make(chan struct{})
    50  	failure := make(chan error)
    51  	outrd, outwr := io.Pipe()
    52  	errrd, errwr := io.Pipe()
    53  	go func() {
    54  		err := m.client.AttachToContainer(docker.AttachToContainerOptions{
    55  			Container:    id,
    56  			OutputStream: outwr,
    57  			ErrorStream:  errwr,
    58  			Stdin:        false,
    59  			Stdout:       true,
    60  			Stderr:       true,
    61  			Stream:       true,
    62  			Success:      success,
    63  		})
    64  		outwr.Close()
    65  		errwr.Close()
    66  		debug("attach:", id, "finished")
    67  		if err != nil {
    68  			close(success)
    69  			failure <- err
    70  		}
    71  		m.send(&AttachEvent{Type: "detach", ID: id, Name: name})
    72  		m.Lock()
    73  		delete(m.attached, id)
    74  		m.Unlock()
    75  	}()
    76  	_, ok := <-success
    77  	if ok {
    78  		m.Lock()
    79  		m.attached[id] = NewLogPump(outrd, errrd, id, name)
    80  		m.Unlock()
    81  		success <- struct{}{}
    82  		m.send(&AttachEvent{ID: id, Name: name, Type: "attach"})
    83  		debug("attach:", id, name, "success")
    84  		return
    85  	}
    86  	debug("attach:", id, "failure:", <-failure)
    87  }
    88  
    89  func (m *AttachManager) send(event *AttachEvent) {
    90  	m.Lock()
    91  	defer m.Unlock()
    92  	for ch, _ := range m.channels {
    93  		// TODO: log err after timeout and continue
    94  		ch <- event
    95  	}
    96  }
    97  
    98  func (m *AttachManager) addListener(ch chan *AttachEvent) {
    99  	m.Lock()
   100  	defer m.Unlock()
   101  	m.channels[ch] = struct{}{}
   102  	go func() {
   103  		for id, pump := range m.attached {
   104  			ch <- &AttachEvent{ID: id, Name: pump.Name, Type: "attach"}
   105  		}
   106  	}()
   107  }
   108  
   109  func (m *AttachManager) removeListener(ch chan *AttachEvent) {
   110  	m.Lock()
   111  	defer m.Unlock()
   112  	delete(m.channels, ch)
   113  }
   114  
   115  func (m *AttachManager) Get(id string) *LogPump {
   116  	m.Lock()
   117  	defer m.Unlock()
   118  	return m.attached[id]
   119  }
   120  
   121  func (m *AttachManager) Listen(source *Source, logstream chan *Log, closer <-chan bool) {
   122  	if source == nil {
   123  		source = new(Source)
   124  	}
   125  	depth := len(m.attached)
   126  	if depth == 0 {
   127  		depth = 1
   128  	}
   129  	events := make(chan *AttachEvent, depth)
   130  	m.addListener(events)
   131  	defer m.removeListener(events)
   132  	for {
   133  		select {
   134  		case event := <-events:
   135  			if event.Type == "attach" && (source.All() ||
   136  				(source.ID != "" && strings.HasPrefix(event.ID, source.ID)) ||
   137  				(source.Name != "" && event.Name == source.Name) ||
   138  				(source.Filter != "" && strings.Contains(event.Name, source.Filter))) {
   139  				pump := m.Get(event.ID)
   140  				pump.AddListener(logstream)
   141  				defer func() {
   142  					if pump != nil {
   143  						pump.RemoveListener(logstream)
   144  					}
   145  				}()
   146  			} else if source.ID != "" && event.Type == "detach" &&
   147  				strings.HasPrefix(event.ID, source.ID) {
   148  				return
   149  			}
   150  		case <-closer:
   151  			return
   152  		}
   153  	}
   154  }
   155  
   156  type LogPump struct {
   157  	sync.Mutex
   158  	ID       string
   159  	Name     string
   160  	channels map[chan *Log]struct{}
   161  }
   162  
   163  func NewLogPump(stdout, stderr io.Reader, id, name string) *LogPump {
   164  	obj := &LogPump{
   165  		ID:       id,
   166  		Name:     name,
   167  		channels: make(map[chan *Log]struct{}),
   168  	}
   169  	pump := func(typ string, source io.Reader) {
   170  		buf := bufio.NewReader(source)
   171  		for {
   172  			data, err := buf.ReadBytes('\n')
   173  			if err != nil {
   174  				if err != io.EOF {
   175  					debug("pump:", id, typ+":", err)
   176  				}
   177  				return
   178  			}
   179  			obj.send(&Log{
   180  				Data: strings.TrimSuffix(string(data), "\n"),
   181  				ID:   id,
   182  				Name: name,
   183  				Type: typ,
   184  			})
   185  		}
   186  	}
   187  	go pump("stdout", stdout)
   188  	go pump("stderr", stderr)
   189  	return obj
   190  }
   191  
   192  func (o *LogPump) send(log *Log) {
   193  	o.Lock()
   194  	defer o.Unlock()
   195  	for ch, _ := range o.channels {
   196  		// TODO: log err after timeout and continue
   197  		ch <- log
   198  	}
   199  }
   200  
   201  func (o *LogPump) AddListener(ch chan *Log) {
   202  	o.Lock()
   203  	defer o.Unlock()
   204  	o.channels[ch] = struct{}{}
   205  }
   206  
   207  func (o *LogPump) RemoveListener(ch chan *Log) {
   208  	o.Lock()
   209  	defer o.Unlock()
   210  	delete(o.channels, ch)
   211  }