func (p *JSONProgress) String() string { var ( width = 200 pbBox string numbersBox string timeLeftBox string ) ws, err := term.GetWinsize(p.terminalFd) if err == nil { width = int(ws.Width) } if p.Current <= 0 && p.Total <= 0 { return "" } current := units.HumanSize(float64(p.Current)) if p.Total <= 0 { return fmt.Sprintf("%8v", current) } total := units.HumanSize(float64(p.Total)) percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 if percentage > 50 { percentage = 50 } if width > 110 { // this number can't be negetive gh#7136 numSpaces := 0 if 50-percentage > 0 { numSpaces = 50 - percentage } pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) } numbersBox = fmt.Sprintf("%8v/%v", current, total) if p.Current > p.Total { // remove total display if the reported current is wonky. numbersBox = fmt.Sprintf("%8v", current) } if p.Current > 0 && p.Start > 0 && percentage < 50 { fromStart := time.Now().UTC().Sub(time.Unix(p.Start, 0)) perEntry := fromStart / time.Duration(p.Current) left := time.Duration(p.Total-p.Current) * perEntry left = (left / time.Second) * time.Second if width > 50 { timeLeftBox = " " + left.String() } } return pbBox + numbersBox + timeLeftBox }
func (cli *DockerCli) getTtySize() (int, int) { if !cli.isTerminalOut { return 0, 0 } ws, err := term.GetWinsize(cli.outFd) if err != nil { log.Debugf("Error getting size: %s", err) if ws == nil { return 0, 0 } } return int(ws.Height), int(ws.Width) }
func runRun(args *docopt.Args, client *cluster.Client) error { cmd := exec.Cmd{ Artifact: exec.DockerImage(args.String["<image>"]), Job: &host.Job{ Config: host.ContainerConfig{ Entrypoint: []string{args.String["<command>"]}, Cmd: args.All["<argument>"].([]string), TTY: term.IsTerminal(os.Stdin.Fd()) && term.IsTerminal(os.Stdout.Fd()), Stdin: true, }, }, HostID: args.String["--host"], Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, } if cmd.Job.Config.TTY { ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return err } cmd.TermHeight = ws.Height cmd.TermWidth = ws.Width cmd.Env = map[string]string{ "COLUMNS": strconv.Itoa(int(ws.Width)), "LINES": strconv.Itoa(int(ws.Height)), "TERM": os.Getenv("TERM"), } } if specs := args.String["--bind"]; specs != "" { mounts := strings.Split(specs, ",") cmd.Job.Config.Mounts = make([]host.Mount, len(mounts)) for i, m := range mounts { s := strings.SplitN(m, ":", 2) cmd.Job.Config.Mounts[i] = host.Mount{ Target: s[0], Location: s[1], Writeable: true, } } } var termState *term.State if cmd.Job.Config.TTY { var err error termState, err = term.MakeRaw(os.Stdin.Fd()) if err != nil { return err } // Restore the terminal if we return without calling os.Exit defer term.RestoreTerminal(os.Stdin.Fd(), termState) go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGWINCH) for range ch { ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return } cmd.ResizeTTY(ws.Height, ws.Width) cmd.Signal(int(syscall.SIGWINCH)) } }() } go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) sig := <-ch cmd.Signal(int(sig.(syscall.Signal))) time.Sleep(10 * time.Second) cmd.Signal(int(syscall.SIGKILL)) }() err := cmd.Run() if status, ok := err.(exec.ExitError); ok { if cmd.Job.Config.TTY { // The deferred restore doesn't happen due to the exit below term.RestoreTerminal(os.Stdin.Fd(), termState) } os.Exit(int(status)) } return err }
func runJob(client *controller.Client, config runConfig) error { req := &ct.NewJob{ Cmd: config.Args, TTY: config.Stdin == nil && config.Stdout == nil && term.IsTerminal(os.Stdin.Fd()) && term.IsTerminal(os.Stdout.Fd()) && !config.Detached, ReleaseID: config.Release, Entrypoint: config.Entrypoint, Env: config.Env, ReleaseEnv: config.ReleaseEnv, DisableLog: config.DisableLog, } if config.Stdin == nil { config.Stdin = os.Stdin } if config.Stdout == nil { config.Stdout = os.Stdout } if config.Stderr == nil { config.Stderr = os.Stderr } if req.TTY { if req.Env == nil { req.Env = make(map[string]string) } ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return err } req.Columns = int(ws.Width) req.Lines = int(ws.Height) req.Env["COLUMNS"] = strconv.Itoa(int(ws.Width)) req.Env["LINES"] = strconv.Itoa(int(ws.Height)) req.Env["TERM"] = os.Getenv("TERM") } if config.Detached { job, err := client.RunJobDetached(config.App, req) if err != nil { return err } log.Println(job.ID) return nil } rwc, err := client.RunJobAttached(config.App, req) if err != nil { return err } defer rwc.Close() attachClient := cluster.NewAttachClient(rwc) var termState *term.State if req.TTY { termState, err = term.MakeRaw(os.Stdin.Fd()) if err != nil { return err } // Restore the terminal if we return without calling os.Exit defer term.RestoreTerminal(os.Stdin.Fd(), termState) go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, SIGWINCH) for range ch { ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return } attachClient.ResizeTTY(ws.Height, ws.Width) attachClient.Signal(int(SIGWINCH)) } }() } go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) sig := <-ch attachClient.Signal(int(sig.(syscall.Signal))) time.Sleep(10 * time.Second) attachClient.Signal(int(syscall.SIGKILL)) }() go func() { io.Copy(attachClient, config.Stdin) attachClient.CloseWrite() }() childDone := make(chan struct{}) shutdown.BeforeExit(func() { <-childDone }) exitStatus, err := attachClient.Receive(config.Stdout, config.Stderr) close(childDone) if err != nil { return err } if req.TTY { term.RestoreTerminal(os.Stdin.Fd(), termState) } if config.Exit { shutdown.ExitWithCode(exitStatus) } if exitStatus != 0 { return RunExitError(exitStatus) } return nil }