github.com/amrnt/deis@v1.3.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 events := make(chan *AttachEvent) 126 m.addListener(events) 127 defer m.removeListener(events) 128 for { 129 select { 130 case event := <-events: 131 if event.Type == "attach" && (source.All() || 132 (source.ID != "" && strings.HasPrefix(event.ID, source.ID)) || 133 (source.Name != "" && event.Name == source.Name) || 134 (source.Filter != "" && strings.Contains(event.Name, source.Filter))) { 135 pump := m.Get(event.ID) 136 pump.AddListener(logstream) 137 defer func() { 138 if pump != nil { 139 pump.RemoveListener(logstream) 140 } 141 }() 142 } else if source.ID != "" && event.Type == "detach" && 143 strings.HasPrefix(event.ID, source.ID) { 144 return 145 } 146 case <-closer: 147 return 148 } 149 } 150 } 151 152 type LogPump struct { 153 sync.Mutex 154 ID string 155 Name string 156 channels map[chan *Log]struct{} 157 } 158 159 func NewLogPump(stdout, stderr io.Reader, id, name string) *LogPump { 160 obj := &LogPump{ 161 ID: id, 162 Name: name, 163 channels: make(map[chan *Log]struct{}), 164 } 165 pump := func(typ string, source io.Reader) { 166 buf := bufio.NewReader(source) 167 for { 168 data, err := buf.ReadBytes('\n') 169 if err != nil { 170 if err != io.EOF { 171 debug("pump:", id, typ+":", err) 172 } 173 return 174 } 175 obj.send(&Log{ 176 Data: strings.TrimSuffix(string(data), "\n"), 177 ID: id, 178 Name: name, 179 Type: typ, 180 }) 181 } 182 } 183 go pump("stdout", stdout) 184 go pump("stderr", stderr) 185 return obj 186 } 187 188 func (o *LogPump) send(log *Log) { 189 o.Lock() 190 defer o.Unlock() 191 for ch, _ := range o.channels { 192 // TODO: log err after timeout and continue 193 ch <- log 194 } 195 } 196 197 func (o *LogPump) AddListener(ch chan *Log) { 198 o.Lock() 199 defer o.Unlock() 200 o.channels[ch] = struct{}{} 201 } 202 203 func (o *LogPump) RemoveListener(ch chan *Log) { 204 o.Lock() 205 defer o.Unlock() 206 delete(o.channels, ch) 207 }