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 }