// handleConn handles an individual client connection. // // It manages the connection, but passes channels on to `answer()`. func (s *server) handleConn(conn net.Conn, conf *ssh.ServerConfig) { defer conn.Close() log.Info(s.c, "Accepted connection.") _, chans, reqs, err := ssh.NewServerConn(conn, conf) if err != nil { // Handshake failure. log.Errf(s.c, "Failed handshake: %s (%v)", err, conn) return } // Discard global requests. We're only concerned with channels. safely.GoDo(s.c, func() { ssh.DiscardRequests(reqs) }) condata := sshConnection(conn) // Now we handle the channels. for incoming := range chans { log.Infof(s.c, "Channel type: %s\n", incoming.ChannelType()) if incoming.ChannelType() != "session" { incoming.Reject(ssh.UnknownChannelType, "Unknown channel type") } channel, req, err := incoming.Accept() if err != nil { // Should close request and move on. panic(err) } safely.GoDo(s.c, func() { s.answer(channel, req, condata) }) } conn.Close() }
// Watch watches a given path, and executes a git check-repos for each event. // // It starts the watcher and then returns. The watcher runs on its own // goroutine. To stop the watching, send the returned channel a bool. // // Params: // - client (Watcher): An Etcd client. // - path (string): The path to watch func Watch(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { // etcdctl -C $ETCD watch --recursive /deis/services path := p.Get("path", "/deis/services").(string) cli := p.Get("client", nil).(client.Client) k := client.NewKeysAPI(cli) watcher := k.Watcher(path, &client.WatcherOptions{Recursive: true}) safely.GoDo(c, func() { for { // TODO: We should probably add cancellation support. response, err := watcher.Next(dctx()) if err != nil { log.Errf(c, "Etcd Watch failed: %s", err) } if response.Node == nil { log.Infof(c, "Unexpected Etcd message: %v", response) } git := exec.Command("/home/git/check-repos") if out, err := git.CombinedOutput(); err != nil { log.Errf(c, "Failed git check-repos: %s", err) log.Infof(c, "Output: %s", out) } } }) return nil, nil }
// ParallelBuild runs multiple docker builds at the same time. // // Params: // -images ([]BuildImg): Images to build // -alwaysFetch (bool): Default false. If set to true, this will always fetch // the Docker image even if it already exists in the registry. // // Returns: // // - Waiter: A *sync.WaitGroup that is waiting for the docker downloads to finish. // // Context: // // This puts 'ParallelBuild.failN" (int) into the context to indicate how many failures // occurred during fetches. func ParallelBuild(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { images := p.Get("images", []BuildImg{}).([]BuildImg) var wg sync.WaitGroup var m sync.Mutex var fails int for _, img := range images { img := img wg.Add(1) safely.GoDo(c, func() { log.Infof(c, "Starting build for %s (tag: %s)", img.Path, img.Tag) if _, err := buildImg(c, img.Path, img.Tag); err != nil { log.Errf(c, "Failed to build docker image: %s", err) m.Lock() fails++ m.Unlock() } wg.Done() }) } // Number of failures. c.Put("ParallelBuild.failN", fails) return &wg, nil }
// ParallelBuild runs multiple docker builds at the same time. // // Params: // -images ([]BuildImg): Images to build // -alwaysFetch (bool): Default false. If set to true, this will always fetch // the Docker image even if it already exists in the registry. // // Returns: // // - Waiter: A *sync.WaitGroup that is waiting for the docker downloads to finish. // // Context: // // This puts 'ParallelBuild.failN" (int) into the context to indicate how many failures // occurred during fetches. func ParallelBuild(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { images := p.Get("images", []BuildImg{}).([]BuildImg) var wg sync.WaitGroup var m sync.Mutex var fails int for _, img := range images { img := img // HACK: ensure "docker build" is serialized by allowing only one entry in // the WaitGroup. This works around the "simultaneous docker pull" bug. wg.Wait() wg.Add(1) safely.GoDo(c, func() { log.Infof(c, "Starting build for %s (tag: %s)", img.Path, img.Tag) if _, err := buildImg(c, img.Path, img.Tag); err != nil { log.Errf(c, "Failed to build docker image: %s", err) m.Lock() fails++ m.Unlock() } wg.Done() }) } // Number of failures. c.Put("ParallelBuild.failN", fails) return &wg, nil }
// Watch watches a given path, and executes a git check-repos for each event. // // It starts the watcher and then returns. The watcher runs on its own // goroutine. To stop the watching, send the returned channel a bool. // // Params: // - client (Watcher): An Etcd client. // - path (string): The path to watch // // Returns: // - chan bool: Send this a message to stop the watcher. func Watch(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { // etcdctl -C $ETCD watch --recursive /deis/services path := p.Get("path", "/deis/services").(string) cli, ok := p.Has("client") if !ok { return nil, errors.New("No etcd client found.") } client := cli.(Watcher) // Stupid hack because etcd watch seems to be broken, constantly complaining // that the JSON it received is malformed. safely.GoDo(c, func() { for { response, err := client.Watch(path, 0, true, nil, nil) if err != nil { log.Errf(c, "Etcd Watch failed: %s", err) time.Sleep(50 * time.Millisecond) continue } if response.Node == nil { log.Infof(c, "Unexpected Etcd message: %v", response) } git := exec.Command("/home/git/check-repos") if out, err := git.CombinedOutput(); err != nil { log.Errf(c, "Failed git check-repos: %s", err) log.Infof(c, "Output: %s", out) } } }) return nil, nil }
// UpdateHostPort intermittently notifies etcd of the builder's address. // // If `port` is specified, this will notify etcd at 10 second intervals that // the builder is listening at $HOST:$PORT, setting the TTL to 20 seconds. // // This will notify etcd as long as the local sshd is running. // // Params: // - base (string): The base path to write the data: $base/host and $base/port. // - host (string): The hostname // - port (string): The port // - client (Setter): The client to use to write the data to etcd. // - sshPid (int): The PID for SSHD. If SSHD dies, this stops notifying. func UpdateHostPort(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { base := p.Get("base", "").(string) host := p.Get("host", "").(string) port := p.Get("port", "").(string) client := p.Get("client", nil).(Setter) sshd := p.Get("sshdPid", 0).(int) // If no port is specified, we don't do anything. if len(port) == 0 { log.Infof(c, "No external port provided. Not publishing details.") return false, nil } var ttl uint64 = 20 if err := setHostPort(client, base, host, port, ttl); err != nil { log.Errf(c, "Etcd error setting host/port: %s", err) return false, err } // Update etcd every ten seconds with this builder's host/port. safely.GoDo(c, func() { ticker := time.NewTicker(10 * time.Second) for range ticker.C { //log.Infof(c, "Setting SSHD host/port") if _, err := os.FindProcess(sshd); err != nil { log.Errf(c, "Lost SSHd process: %s", err) break } else { if err := setHostPort(client, base, host, port, ttl); err != nil { log.Errf(c, "Etcd error setting host/port: %s", err) break } } } ticker.Stop() }) return true, nil }
// listen handles accepting and managing connections. However, since closer // is len(1), it will not block the sender. func (s *server) listen(l net.Listener, conf *ssh.ServerConfig, closer chan interface{}) error { cxt := s.c log.Info(cxt, "Accepting new connections.") defer l.Close() // FIXME: Since Accept blocks, closer may not be checked often enough. for { log.Info(cxt, "Checking closer.") if len(closer) > 0 { <-closer log.Info(cxt, "Shutting down SSHD listener.") return nil } conn, err := l.Accept() if err != nil { log.Warnf(cxt, "Error during Accept: %s", err) // We shouldn't kill the listener because of an error. return err } safely.GoDo(cxt, func() { s.handleConn(conn, conf) }) } }
// KillOnExit kills PIDs when the program exits. // // Otherwise, this blocks until an os.Interrupt or os.Kill is received. // // Params: // This treats Params as a map of process names (unimportant) to PIDs. It then // attempts to kill all of the pids that it receives. func KillOnExit(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { sigs := make(chan os.Signal, 1) signal.Notify(sigs, os.Interrupt, os.Kill) safely.GoDo(c, func() { log.Info(c, "Builder is running.") <-sigs c.Log("info", "Builder received signal to stop.") pids := p.AsMap() killed := 0 for name, pid := range pids { if pid, ok := pid.(int); ok { if proc, err := os.FindProcess(pid); err == nil { log.Infof(c, "Killing %s (pid=%d)", name, pid) proc.Kill() killed++ } } } }) return nil, nil }
// Watch watches a given path, and executes a git check-repos for each event. // // It starts the watcher and then returns. The watcher runs on its own // goroutine. To stop the watching, send the returned channel a bool. // // Params: // - client (Watcher): An Etcd client. // - path (string): The path to watch // // Returns: // - chan bool: Send this a message to stop the watcher. func Watch(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { // etcdctl -C $ETCD watch --recursive /deis/services path := p.Get("path", "/deis/services").(string) cli, ok := p.Has("client") if !ok { return nil, errors.New("No etcd client found.") } client := cli.(Watcher) // Stupid hack because etcd watch seems to be broken, constantly complaining // that the JSON it received is malformed. safely.GoDo(c, func() { for { response, err := client.Watch(path, 0, true, nil, nil) if err != nil { log.Errf(c, "Etcd Watch failed: %s", err) time.Sleep(50 * time.Millisecond) continue } if response.Node == nil { log.Infof(c, "Unexpected Etcd message: %v", response) } git := exec.Command("/home/git/check-repos") if out, err := git.CombinedOutput(); err != nil { log.Errf(c, "Failed git check-repos: %s", err) log.Infof(c, "Output: %s", out) } } }) return nil, nil /* Watch seems to be broken. So we do this stupid watch loop instead. receiver := make(chan *etcd.Response) stop := make(chan bool) // Buffer the channels so that we don't hang waiting for go-etcd to // read off the channel. stopetcd := make(chan bool, 1) stopwatch := make(chan bool, 1) // Watch for errors. safely.GoDo(c, func() { // When a receiver is passed in, no *Response is ever returned. Instead, // Watch acts like an error channel, and receiver gets all of the messages. _, err := client.Watch(path, 0, true, receiver, stopetcd) if err != nil { log.Infof(c, "Watcher stopped with error '%s'", err) stopwatch <- true //close(stopwatch) } }) // Watch for events safely.GoDo(c, func() { for { select { case msg := <-receiver: if msg.Node != nil { log.Infof(c, "Received notification %s for %s", msg.Action, msg.Node.Key) } else { log.Infof(c, "Received unexpected etcd message: %v", msg) } git := exec.Command("/home/git/check-repos") if out, err := git.CombinedOutput(); err != nil { log.Errf(c, "Failed git check-repos: %s", err) log.Infof(c, "Output: %s", out) } case <-stopwatch: c.Logf("debug", "Received signal to stop watching events.") return } } }) // Fan out stop requests. safely.GoDo(c, func() { <-stop stopwatch <- true stopetcd <- true close(stopwatch) close(stopetcd) }) return stop, nil */ }