// RunBuilder starts the Builder service. // // The Builder service is responsible for setting up the local container // environment and then listening for new builds. The main listening service // is SSH. Builder listens for new Git commands and then sends those on to // Git. // // Run returns on of the Status* status code constants. func RunBuilder(cnf *sshd.Config, gitHomeDir string, sshServerCircuit *sshd.Circuit, pushLock sshd.RepositoryLock) int { address := fmt.Sprintf("%s:%d", cnf.SSHHostIP, cnf.SSHHostPort) cfg, err := sshd.Configure(cnf) if err != nil { log.Err("SSH server configuration failed: %s", err) return StatusLocalError } receivetype := "gitreceive" if err := sshd.Serve(cfg, sshServerCircuit, gitHomeDir, pushLock, address, receivetype); err != nil { log.Err("SSH server failed: %s", err) return StatusLocalError } return StatusOk }
func main() { if os.Getenv("DEBUG") == "true" { pkglog.DefaultLogger.SetDebug(true) cookoolog.Level = cookoolog.LogDebug } pkglog.Debug("Running in debug mode") app := cli.NewApp() app.Commands = []cli.Command{ { Name: "server", Aliases: []string{"srv"}, Usage: "Run the git server", Action: func(c *cli.Context) { cnf := new(sshd.Config) if err := conf.EnvConfig(serverConfAppName, cnf); err != nil { pkglog.Err("getting config for %s [%s]", serverConfAppName, err) os.Exit(1) } pkglog.Info("starting fetcher on port %d", cnf.FetcherPort) go fetcher.Serve(cnf.FetcherPort) pkglog.Info("starting SSH server on %s:%d", cnf.SSHHostIP, cnf.SSHHostPort) os.Exit(pkg.Run(cnf.SSHHostIP, cnf.SSHHostPort, "boot")) }, }, { Name: "git-receive", Aliases: []string{"gr"}, Usage: "Run the git-receive hook", Action: func(c *cli.Context) { cnf := new(gitreceive.Config) if err := conf.EnvConfig(gitReceiveConfAppName, cnf); err != nil { pkglog.Err("Error getting config for %s [%s]", gitReceiveConfAppName, err) os.Exit(1) } cnf.CheckDurations() if err := gitreceive.Run(cnf); err != nil { pkglog.Err("running git receive hook [%s]", err) os.Exit(1) } }, }, } app.Run(os.Args) }
// 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("Accepted connection.") sshConn, chans, reqs, err := ssh.NewServerConn(conn, conf) if err != nil { // Handshake failure. log.Err("Failed handshake: %s", err) return } // Discard global requests. We're only concerned with channels. go ssh.DiscardRequests(reqs) condata := sshConnection(conn) // Now we handle the channels. for incoming := range chans { log.Info("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) } go s.answer(channel, req, condata, sshConn) } conn.Close() }
// createRepo creates a new Git repo if it is not present already. // // Largely inspired by gitreceived from Flynn. // // Returns a bool indicating whether a project was created (true) or already // existed (false). func createRepo(repoPath string) (bool, error) { createLock.Lock() defer createLock.Unlock() fi, err := os.Stat(repoPath) if err == nil && fi.IsDir() { // Nothing to do. log.Debug("Directory %s already exists.", repoPath) return false, nil } else if os.IsNotExist(err) { log.Debug("Creating new directory at %s", repoPath) // Create directory if err := os.MkdirAll(repoPath, 0755); err != nil { log.Err("Failed to create repository: %s", err) return false, err } cmd := exec.Command("git", "init", "--bare") cmd.Dir = repoPath if out, err := cmd.CombinedOutput(); err != nil { log.Info("git init output: %s", out) return false, err } return true, nil } else if err == nil { return false, errors.New("Expected directory, found file.") } return false, err }
// Ping handles a simple test SSH exec. // // Returns the string PONG and exit status 0. // // Params: // - channel (ssh.Channel): The channel to respond on. // - request (*ssh.Request): The request. // func Ping(channel ssh.Channel, req *ssh.Request) error { log.Info("PING") if _, err := channel.Write([]byte("pong")); err != nil { log.Err("Failed to write to channel: %s", err) } sendExitStatus(0, channel) req.Reply(true, nil) return 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) error { log.Info("Accepting new connections.") defer l.Close() for { conn, err := l.Accept() if err != nil { log.Err("Error during Accept: %s", err) // We shut down the listener if Accept errors return err } go s.handleConn(conn, conf) } }
// 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 { log.Info("Accepting new connections.") defer l.Close() // FIXME: Since Accept blocks, closer may not be checked often enough. for { log.Info("Checking closer.") if len(closer) > 0 { <-closer log.Info("Shutting down SSHD listener.") return nil } conn, err := l.Accept() if err != nil { log.Err("Error during Accept: %s", err) // We shouldn't kill the listener because of an error. return err } go s.handleConn(conn, conf) } }
// answer handles answering requests and channel requests // // Currently, an exec must be either "ping", "git-receive-pack" or // "git-upload-pack". Anything else will result in a failure response. Right // now, we leave the channel open on failure because it is unclear what the // correct behavior for a failed exec is. // // Support for setting environment variables via `env` has been disabled. func (s *server) answer(channel ssh.Channel, requests <-chan *ssh.Request, condata string, sshconn *ssh.ServerConn) error { defer channel.Close() // Answer all the requests on this connection. for req := range requests { ok := false switch req.Type { case "env": o := &EnvVar{} ssh.Unmarshal(req.Payload, o) log.Info("Key='%s', Value='%s'\n", o.Name, o.Value) req.Reply(true, nil) case "exec": clean := cleanExec(req.Payload) parts := strings.SplitN(clean, " ", 2) switch parts[0] { case "ping": err := Ping(channel, req) if err != nil { log.Info("Error pinging: %s", err) } return err case "git-receive-pack", "git-upload-pack": if len(parts) < 2 { log.Info("Expected two-part command.") req.Reply(ok, nil) break } repoName, err := cleanRepoName(parts[1]) if err != nil { log.Err("Illegal repo name: %s.", err) channel.Stderr().Write([]byte("No repo given")) return err } wrapErr := wrapInLock(s.pushLock, repoName, time.Duration(0), s.runReceive(req, sshconn, channel, repoName, parts, condata)) if wrapErr == errAlreadyLocked { log.Info(multiplePush) // The error must be in git format if pktErr := gitPktLine(channel, fmt.Sprintf("ERR %v\n", multiplePush)); pktErr != nil { log.Err("Failed to write to channel: %s", err) } sendExitStatus(1, channel) req.Reply(false, nil) return nil } var xs uint32 if wrapErr != nil { log.Err("Failed git receive: %v", err) xs = 1 } sendExitStatus(xs, channel) return nil default: log.Info("Illegal command is '%s'\n", clean) req.Reply(false, nil) return nil } if err := sendExitStatus(0, channel); err != nil { log.Err("Failed to write exit status: %s", err) } return nil default: // We simply ignore all of the other cases and leave the // channel open to take additional requests. log.Info("Received request of type %s\n", req.Type) req.Reply(false, nil) } } return nil }
func main() { if os.Getenv("DEBUG") == "true" { pkglog.DefaultLogger.SetDebug(true) cookoolog.Level = cookoolog.LogDebug log.Printf("Running in debug mode") } app := cli.NewApp() app.Commands = []cli.Command{ { Name: "server", Aliases: []string{"srv"}, Usage: "Run the git server", Action: func(c *cli.Context) { cnf := new(sshd.Config) if err := conf.EnvConfig(serverConfAppName, cnf); err != nil { pkglog.Err("getting config for %s [%s]", serverConfAppName, err) os.Exit(1) } circ := sshd.NewCircuit() s3Client, err := storage.GetClient(cnf.HealthSrvTestStorageRegion) if err != nil { log.Printf("Error getting s3 client (%s)", err) os.Exit(1) } kubeClient, err := kcl.NewInCluster() if err != nil { log.Printf("Error getting kubernetes client [%s]", err) os.Exit(1) } log.Printf("Starting health check server on port %d", cnf.HealthSrvPort) healthSrvCh := make(chan error) go func() { if err := healthsrv.Start(cnf.HealthSrvPort, kubeClient.Namespaces(), s3Client, circ); err != nil { healthSrvCh <- err } }() log.Printf("Starting SSH server on %s:%d", cnf.SSHHostIP, cnf.SSHHostPort) sshCh := make(chan int) go func() { sshCh <- pkg.RunBuilder(cnf.SSHHostIP, cnf.SSHHostPort, circ) }() select { case err := <-healthSrvCh: log.Printf("Error running health server (%s)", err) os.Exit(1) case i := <-sshCh: log.Printf("Unexpected SSH server stop with code %d", i) os.Exit(i) } }, }, { Name: "git-receive", Aliases: []string{"gr"}, Usage: "Run the git-receive hook", Action: func(c *cli.Context) { cnf := new(gitreceive.Config) if err := conf.EnvConfig(gitReceiveConfAppName, cnf); err != nil { log.Printf("Error getting config for %s [%s]", gitReceiveConfAppName, err) os.Exit(1) } cnf.CheckDurations() if err := gitreceive.Run(cnf); err != nil { log.Printf("Error running git receive hook [%s]", err) os.Exit(1) } }, }, } app.Run(os.Args) }
// Receive receives a Git repo. // This will only work for git-receive-pack. func Receive( repo, operation, gitHome string, channel ssh.Channel, fingerprint, username, conndata, receivetype string) error { log.Info("receiving git repo name: %s, operation: %s, fingerprint: %s, user: %s", repo, operation, fingerprint, username) if receivetype == "mock" { channel.Write([]byte("OK")) return nil } repoPath := filepath.Join(gitHome, repo) log.Info("creating repo directory %s", repoPath) if _, err := createRepo(repoPath); err != nil { err = fmt.Errorf("Did not create new repo (%s)", err) return err } log.Info("writing pre-receive hook under %s", repoPath) if err := createPreReceiveHook(gitHome, repoPath); err != nil { err = fmt.Errorf("Did not write pre-receive hook (%s)", err) return err } cmd := exec.Command("git-shell", "-c", fmt.Sprintf("%s '%s'", operation, repo)) log.Info(strings.Join(cmd.Args, " ")) var errbuff bytes.Buffer cmd.Dir = gitHome cmd.Env = []string{ fmt.Sprintf("RECEIVE_USER=%s", username), fmt.Sprintf("RECEIVE_REPO=%s", repo), fmt.Sprintf("RECEIVE_FINGERPRINT=%s", fingerprint), fmt.Sprintf("SSH_ORIGINAL_COMMAND=%s '%s'", operation, repo), fmt.Sprintf("SSH_CONNECTION=%s", conndata), } cmd.Env = append(cmd.Env, os.Environ()...) log.Debug("Working Dir: %s", cmd.Dir) log.Debug("Environment: %s", strings.Join(cmd.Env, ",")) inpipe, err := cmd.StdinPipe() if err != nil { return err } cmd.Stdout = channel cmd.Stderr = io.MultiWriter(channel.Stderr(), &errbuff) if err := cmd.Start(); err != nil { err = fmt.Errorf("Failed to start git pre-receive hook: %s (%s)", err, errbuff.Bytes()) return err } if _, err := io.Copy(inpipe, channel); err != nil { err = fmt.Errorf("Failed to write git objects into the git pre-receive hook (%s)", err) return err } fmt.Println("Waiting for git-receive to run.") fmt.Println("Waiting for deploy.") if err := cmd.Wait(); err != nil { err = fmt.Errorf("Failed to run git pre-receive hook: %s (%s)", errbuff.Bytes(), err) return err } if errbuff.Len() > 0 { log.Err("Unreported error: %s", errbuff.Bytes()) return errors.New(errbuff.String()) } log.Info("Deploy complete.") return nil }
func main() { if os.Getenv("DEBUG") == "true" { pkglog.DefaultLogger.SetDebug(true) log.Printf("Running in debug mode") } app := cli.NewApp() app.Commands = []cli.Command{ { Name: "server", Aliases: []string{"srv"}, Usage: "Run the git server", Action: func(c *cli.Context) { cnf := new(sshd.Config) if err := conf.EnvConfig(serverConfAppName, cnf); err != nil { pkglog.Err("getting config for %s [%s]", serverConfAppName, err) os.Exit(1) } fs := sys.RealFS() env := sys.RealEnv() pushLock := sshd.NewInMemoryRepositoryLock() circ := sshd.NewCircuit() storageParams, err := conf.GetStorageParams(env) if err != nil { log.Printf("Error getting storage parameters (%s)", err) os.Exit(1) } var storageDriver storagedriver.StorageDriver if cnf.StorageType == "minio" { storageDriver, err = factory.Create("s3", storageParams) } else { storageDriver, err = factory.Create(cnf.StorageType, storageParams) } if err != nil { log.Printf("Error creating storage driver (%s)", err) os.Exit(1) } kubeClient, err := kcl.NewInCluster() if err != nil { log.Printf("Error getting kubernetes client [%s]", err) os.Exit(1) } log.Printf("Starting health check server on port %d", cnf.HealthSrvPort) healthSrvCh := make(chan error) go func() { if err := healthsrv.Start(cnf.HealthSrvPort, kubeClient.Namespaces(), storageDriver, circ); err != nil { healthSrvCh <- err } }() log.Printf("Starting deleted app cleaner") cleanerErrCh := make(chan error) go func() { if err := cleaner.Run(gitHomeDir, kubeClient.Namespaces(), fs, cnf.CleanerPollSleepDuration()); err != nil { cleanerErrCh <- err } }() log.Printf("Starting SSH server on %s:%d", cnf.SSHHostIP, cnf.SSHHostPort) sshCh := make(chan int) go func() { sshCh <- pkg.RunBuilder(cnf.SSHHostIP, cnf.SSHHostPort, gitHomeDir, circ, pushLock) }() select { case err := <-healthSrvCh: log.Printf("Error running health server (%s)", err) os.Exit(1) case i := <-sshCh: log.Printf("Unexpected SSH server stop with code %d", i) os.Exit(i) case err := <-cleanerErrCh: log.Printf("Error running the deleted app cleaner (%s)", err) os.Exit(1) } }, }, { Name: "git-receive", Aliases: []string{"gr"}, Usage: "Run the git-receive hook", Action: func(c *cli.Context) { cnf := new(gitreceive.Config) if err := conf.EnvConfig(gitReceiveConfAppName, cnf); err != nil { log.Printf("Error getting config for %s [%s]", gitReceiveConfAppName, err) os.Exit(1) } cnf.CheckDurations() fs := sys.RealFS() env := sys.RealEnv() storageParams, err := conf.GetStorageParams(env) if err != nil { log.Printf("Error getting storage parameters (%s)", err) os.Exit(1) } var storageDriver storagedriver.StorageDriver if cnf.StorageType == "minio" { storageDriver, err = factory.Create("s3", storageParams) } else { storageDriver, err = factory.Create(cnf.StorageType, storageParams) } if err != nil { log.Printf("Error creating storage driver (%s)", err) os.Exit(1) } if err := gitreceive.Run(cnf, fs, env, storageDriver); err != nil { log.Printf("Error running git receive hook [%s]", err) os.Exit(1) } }, }, } app.Run(os.Args) }