// Safe invokes the provided function and will attempt to ensure that when the // function returns (or a termination signal is sent) that the terminal state // is reset to the condition it was in prior to the function being invoked. If // t.Raw is true the terminal will be put into raw mode prior to calling the function. // If the input file descriptor is not a TTY and TryDev is true, the /dev/tty file // will be opened (if available). func (t TTY) Safe(fn SafeFunc) error { in := t.In var hasFd bool var inFd uintptr if desc, ok := in.(fd); ok && in != nil { inFd = desc.Fd() hasFd = true } if t.TryDev && (!hasFd || !term.IsTerminal(inFd)) { if f, err := os.Open("/dev/tty"); err == nil { defer f.Close() inFd = f.Fd() hasFd = true } } if !hasFd || !term.IsTerminal(inFd) { return fn() } var state *term.State var err error if t.Raw { state, err = term.MakeRaw(inFd) } else { state, err = term.SaveState(inFd) } if err != nil { return err } return interrupt.Chain(t.Parent, func() { term.RestoreTerminal(inFd, state) }).Run(fn) }
// withSafeTTYAndInterrupts invokes the provided function after the terminal // state has been stored, and then on any error or termination attempts to // restore the terminal state to its prior behavior. It also eats signals // for the duration of the function. func withSafeTTYAndInterrupts(fn func() error) error { ch := make(chan os.Signal, 1) signal.Notify(ch, childSignals...) defer signal.Stop(ch) inFd := os.Stdin.Fd() if !term.IsTerminal(inFd) { if f, err := os.Open("/dev/tty"); err == nil { defer f.Close() inFd = f.Fd() } } if term.IsTerminal(inFd) { state, err := term.SaveState(inFd) if err != nil { return err } go func() { if _, ok := <-ch; !ok { return } term.RestoreTerminal(inFd, state) }() defer term.RestoreTerminal(inFd, state) return fn() } return fn() }
func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey, proto, addr string, tlsConfig *tls.Config) *DockerCli { var ( inFd uintptr outFd uintptr isTerminalIn = false isTerminalOut = false scheme = "http" ) if tlsConfig != nil { scheme = "https" } if in != nil { if file, ok := in.(*os.File); ok { inFd = file.Fd() isTerminalIn = term.IsTerminal(inFd) } } if out != nil { if file, ok := out.(*os.File); ok { outFd = file.Fd() isTerminalOut = term.IsTerminal(outFd) } } if err == nil { err = out } // The transport is created here for reuse during the client session tr := &http.Transport{ TLSClientConfig: tlsConfig, Dial: func(dial_network, dial_addr string) (net.Conn, error) { // Why 32? See issue 8035 return net.DialTimeout(proto, addr, 32*time.Second) }, } if proto == "unix" { // no need in compressing for local communications tr.DisableCompression = true } return &DockerCli{ proto: proto, addr: addr, in: in, out: out, err: err, key: key, inFd: inFd, outFd: outFd, isTerminalIn: isTerminalIn, isTerminalOut: isTerminalOut, tlsConfig: tlsConfig, scheme: scheme, transport: tr, } }
// NewResponsiveWriter creates a Writer that detects the column width of the // terminal we are in, and adjusts every line width to fit and use recommended // terminal sizes for better readability. Does proper word wrapping automatically. // if terminal width >= 120 columns use 120 columns // if terminal width >= 100 columns use 100 columns // if terminal width >= 80 columns use 80 columns // In case we're not in a terminal or if it's smaller than 80 columns width, // doesn't do any wrapping. func NewResponsiveWriter(w io.Writer) io.Writer { file, ok := w.(*os.File) if !ok { return w } fd := file.Fd() if !term.IsTerminal(fd) { return w } terminalSize := kterm.GetSize(fd) if terminalSize == nil { return w } var limit uint switch { case terminalSize.Width >= 120: limit = 120 case terminalSize.Width >= 100: limit = 100 case terminalSize.Width >= 80: limit = 80 } return NewWordWrapWriter(w, limit) }
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli { var ( isTerminal = false terminalFd uintptr scheme = "http" ) if tlsConfig != nil { scheme = "https" } if in != nil { if file, ok := out.(*os.File); ok { terminalFd = file.Fd() isTerminal = term.IsTerminal(terminalFd) } } if err == nil { err = out } return &DockerCli{ proto: proto, addr: addr, in: in, out: out, err: err, isTerminal: isTerminal, terminalFd: terminalFd, tlsConfig: tlsConfig, scheme: scheme, } }
func runPgRestore(args *docopt.Args, client controller.Client, config *runConfig) error { config.Stdin = os.Stdin var size int64 if filename := args.String["--file"]; filename != "" { f, err := os.Open(filename) if err != nil { return err } defer f.Close() stat, err := f.Stat() if err != nil { return err } size = stat.Size() config.Stdin = f } if !args.Bool["--quiet"] && term.IsTerminal(os.Stderr.Fd()) { bar := pb.New(0) bar.SetUnits(pb.U_BYTES) if size > 0 { bar.Total = size } else { bar.ShowBar = false } bar.ShowSpeed = true bar.Output = os.Stderr bar.Start() defer bar.Finish() config.Stdin = bar.NewProxyReader(config.Stdin) } return pgRestore(client, config) }
// PromptForPasswordString prompts for user input by disabling echo in terminal, useful for password prompt. func PromptForPasswordString(r io.Reader, w io.Writer, format string, a ...interface{}) string { if w == nil { w = os.Stdout } if file, ok := r.(*os.File); ok { inFd := file.Fd() if term.IsTerminal(inFd) { oldState, err := term.SaveState(inFd) if err != nil { glog.V(3).Infof("Unable to save terminal state") return PromptForString(r, w, format, a...) } fmt.Fprintf(w, format, a...) term.DisableEcho(inFd, oldState) input := readInput(r) defer term.RestoreTerminal(inFd, oldState) fmt.Fprintf(w, "\n") return input } glog.V(3).Infof("Stdin is not a terminal") return PromptForString(r, w, format, a...) } return PromptForString(r, w, format, a...) }
func (client *NativeClient) Shell(args ...string) error { var ( termWidth, termHeight int ) conn, err := ssh.Dial("tcp", net.JoinHostPort(client.Hostname, strconv.Itoa(client.Port)), &client.Config) if err != nil { return err } defer closeConn(conn) session, err := conn.NewSession() if err != nil { return err } defer session.Close() session.Stdout = os.Stdout session.Stderr = os.Stderr session.Stdin = os.Stdin modes := ssh.TerminalModes{ ssh.ECHO: 1, } fd := os.Stdin.Fd() if term.IsTerminal(fd) { oldState, err := term.MakeRaw(fd) if err != nil { return err } defer term.RestoreTerminal(fd, oldState) winsize, err := term.GetWinsize(fd) if err != nil { termWidth = 80 termHeight = 24 } else { termWidth = int(winsize.Width) termHeight = int(winsize.Height) } } if err := session.RequestPty("xterm", termHeight, termWidth, modes); err != nil { return err } if len(args) == 0 { if err := session.Shell(); err != nil { return err } session.Wait() } else { session.Run(strings.Join(args, " ")) } return nil }
// Safe invokes the provided function and will attempt to ensure that when the // function returns (or a termination signal is sent) that the terminal state // is reset to the condition it was in prior to the function being invoked. If // t.Raw is true the terminal will be put into raw mode prior to calling the function. // If the input file descriptor is not a TTY and TryDev is true, the /dev/tty file // will be opened (if available). func (t TTY) Safe(fn SafeFunc) error { inFd, isTerminal := term.GetFdInfo(t.In) if !isTerminal && t.TryDev { if f, err := os.Open("/dev/tty"); err == nil { defer f.Close() inFd = f.Fd() isTerminal = term.IsTerminal(inFd) } } if !isTerminal { return fn() } var state *term.State var err error if t.Raw { state, err = term.MakeRaw(inFd) } else { state, err = term.SaveState(inFd) } if err != nil { return err } return interrupt.Chain(t.Parent, func() { if t.sizeQueue != nil { t.sizeQueue.stop() } term.RestoreTerminal(inFd, state) }).Run(fn) }
func runRedisDump(args *docopt.Args, client controller.Client, config *runConfig) error { config.Stdout = os.Stdout if filename := args.String["--file"]; filename != "" { f, err := os.Create(filename) if err != nil { return err } defer f.Close() config.Stdout = f } if !args.Bool["--quiet"] && term.IsTerminal(os.Stderr.Fd()) { bar := pb.New(0) bar.SetUnits(pb.U_BYTES) bar.ShowBar = false bar.ShowSpeed = true bar.Output = os.Stderr bar.Start() defer bar.Finish() config.Stdout = io.MultiWriter(config.Stdout, bar) } config.Args[0] = "/bin/dump-flynn-redis" return runJob(client, *config) }
// PromptRetriever returns a new Retriever which will provide a prompt on stdin // and stdout to retrieve a passphrase. stdin will be checked if it is a terminal, // else the PromptRetriever will error when attempting to retrieve a passphrase. // Upon successful passphrase retrievals, the passphrase will be cached such that // subsequent prompts will produce the same passphrase. func PromptRetriever() notary.PassRetriever { if !term.IsTerminal(os.Stdin.Fd()) { return func(string, string, bool, int) (string, bool, error) { return "", false, ErrNoInput } } return PromptRetrieverWithInOut(os.Stdin, os.Stdout, nil) }
func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey, proto, addr string, tlsConfig *tls.Config) *DockerCli { var ( inFd uintptr outFd uintptr isTerminalIn = false isTerminalOut = false scheme = "http" ) if tlsConfig != nil { scheme = "https" } if in != nil { if file, ok := in.(*os.File); ok { inFd = file.Fd() isTerminalIn = term.IsTerminal(inFd) } } if out != nil { if file, ok := out.(*os.File); ok { outFd = file.Fd() isTerminalOut = term.IsTerminal(outFd) } } if err == nil { err = out } return &DockerCli{ proto: proto, addr: addr, in: in, out: out, err: err, key: key, inFd: inFd, outFd: outFd, isTerminalIn: isTerminalIn, isTerminalOut: isTerminalOut, tlsConfig: tlsConfig, scheme: scheme, } }
// Run executes a validated remote execution against a pod. func (p *AttachOptions) Run() error { pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { return fmt.Errorf("pod %s is not running and cannot be attached to; current phase is %s", p.PodName, pod.Status.Phase) } // TODO: refactor with terminal helpers from the edit utility once that is merged var stdin io.Reader tty := p.TTY if p.Stdin { stdin = p.In if tty { if file, ok := stdin.(*os.File); ok { inFd := file.Fd() if term.IsTerminal(inFd) { oldState, err := term.SetRawTerminal(inFd) if err != nil { glog.Fatal(err) } // this handles a clean exit, where the command finished defer term.RestoreTerminal(inFd, oldState) // SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens // for SIGINT and restores the terminal before exiting) // this handles SIGTERM sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) go func() { <-sigChan term.RestoreTerminal(inFd, oldState) os.Exit(0) }() } else { fmt.Fprintln(p.Err, "STDIN is not a terminal") } } else { tty = false fmt.Fprintln(p.Err, "Unable to use a TTY - input is not the right kind of file") } } } // TODO: consider abstracting into a client invocation or client helper req := p.Client.RESTClient.Post(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("attach"). Param("container", p.GetContainerName(pod)) return p.Attach.Attach("POST", req.URL(), p.Config, stdin, p.Out, p.Err, tty) }
func (client NativeClient) Shell() error { var ( termWidth, termHeight int ) conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Hostname, client.Port), &client.Config) if err != nil { return err } session, err := conn.NewSession() if err != nil { return err } defer session.Close() session.Stdout = os.Stdout session.Stderr = os.Stderr session.Stdin = os.Stdin modes := ssh.TerminalModes{ ssh.ECHO: 1, } fd := os.Stdin.Fd() if term.IsTerminal(fd) { oldState, err := term.MakeRaw(fd) if err != nil { return err } defer term.RestoreTerminal(fd, oldState) winsize, err := term.GetWinsize(fd) if err != nil { termWidth = 80 termHeight = 24 } else { termWidth = int(winsize.Width) termHeight = int(winsize.Height) } } if err := session.RequestPty("xterm", termHeight, termWidth, modes); err != nil { return err } if err := session.Shell(); err != nil { return err } session.Wait() return nil }
func (a *App) Build(e *env.Env, writer io.Writer) error { e.Log.Info(`Build app`) if a.Layer.ID == `` { return errors.New("layer not found") } if err := a.Config.Sync(e, a.Layer.ID); err != nil { return err } tar_path := fmt.Sprintf("%s/apps/%s", env.Default_root_path, a.Layer.ID) reader, err := os.Open(tar_path) if err != nil { return err } or, ow := io.Pipe() opts := interfaces.BuildImageOptions{ Name: a.Config.Image, RmTmpContainer: true, InputStream: reader, OutputStream: ow, RawJSONStream: true, } ch := make(chan error, 1) go func() { defer ow.Close() defer close(ch) if err := e.Containers.BuildImage(opts); err != nil { e.Log.Error(err) return } }() jsonmessage.DisplayJSONMessagesStream(or, writer, os.Stdout.Fd(), term.IsTerminal(os.Stdout.Fd()), nil) if err, ok := <-ch; ok { if err != nil { e.Log.Error(err) return err } } if err := a.Update(e); err != nil { return err } reader.Close() or.Close() return nil }
func (g *Gist) Upload(log log15.Logger) error { if len(g.Files) == 0 { return errors.New("cannot create empty gist") } payload, err := json.Marshal(g) if err != nil { log.Error("error preparing gist content", "err", err) return err } var body io.Reader = bytes.NewReader(payload) if term.IsTerminal(os.Stderr.Fd()) { bar := pb.New(len(payload)) bar.SetUnits(pb.U_BYTES) bar.ShowSpeed = true bar.Output = os.Stderr bar.Start() defer bar.Finish() body = bar.NewProxyReader(body) } req, err := http.NewRequest("POST", "https://api.github.com/gists", body) if err != nil { log.Error("error preparing HTTP request", "err", err) return err } req.Header.Set("Content-Type", "application/json") log.Info("creating anonymous gist") res, err := http.DefaultClient.Do(req) if err != nil { log.Error("error uploading gist content", "err", err) return err } defer res.Body.Close() if res.StatusCode != http.StatusCreated { e := fmt.Sprintf("unexpected HTTP status: %d", res.StatusCode) log.Error(e) return errors.New(e) } if err := json.NewDecoder(res.Body).Decode(g); err != nil { log.Error("error decoding HTTP response", "err", err) return err } return nil }
func (d *Dapperfile) runArgs(tag, shell string, commandArgs []string) (string, []string) { name := fmt.Sprintf("%s-%s", strings.Split(tag, ":")[0], randString()) args := []string{"-i", "--name", name} if term.IsTerminal(0) { args = append(args, "-t") } if d.env.Socket() || d.socket { args = append(args, "-v", "/var/run/docker.sock:/var/run/docker.sock") } if d.IsBind() { wd, err := os.Getwd() if err == nil { args = append(args, "-v", fmt.Sprintf("%s:%s", fmt.Sprintf("%s/%s", wd, d.env.Cp()), d.env.Source())) } } args = append(args, "-e", fmt.Sprintf("DAPPER_UID=%d", os.Getuid())) args = append(args, "-e", fmt.Sprintf("DAPPER_GID=%d", os.Getgid())) for _, env := range d.env.Env() { args = append(args, "-e", env) } if shell != "" { args = append(args, "--entrypoint", shell) args = append(args, "-e", "TERM") } args = append(args, d.env.RunArgs()...) args = append(args, tag) if shell != "" && len(commandArgs) == 0 { args = append(args, "-") } else { args = append(args, commandArgs...) } return name, args }
func runClusterBackup(args *docopt.Args) error { client, err := getClusterClient() if err != nil { return err } var bar *pb.ProgressBar var progress backup.ProgressBar if term.IsTerminal(os.Stderr.Fd()) { bar = pb.New(0) bar.SetUnits(pb.U_BYTES) bar.ShowBar = false bar.ShowSpeed = true bar.Output = os.Stderr bar.Start() progress = bar } var dest io.Writer = os.Stdout if filename := args.String["--file"]; filename != "" { f, err := os.Create(filename) if err != nil { return err } defer f.Close() dest = f } fmt.Fprintln(os.Stderr, "Creating cluster backup...") if err := backup.Run(client, dest, progress); err != nil { return err } if bar != nil { bar.Finish() } fmt.Fprintln(os.Stderr, "Backup complete.") return nil }
func (ps passwordStore) Basic(u *url.URL) (string, string) { if ps.anonymous { return "", "" } stdin := bufio.NewReader(os.Stdin) fmt.Fprintf(os.Stdout, "Enter username: "******"error processing username input: %s", err) return "", "" } username := strings.TrimSpace(string(userIn)) if term.IsTerminal(0) { state, err := term.SaveState(0) if err != nil { logrus.Errorf("error saving terminal state, cannot retrieve password: %s", err) return "", "" } term.DisableEcho(0, state) defer term.RestoreTerminal(0, state) } fmt.Fprintf(os.Stdout, "Enter password: "******"error processing password input: %s", err) return "", "" } password := strings.TrimSpace(string(userIn)) return username, password }
func (br *boundRetriever) requestPassphrase(keyName, alias string, createNew bool, numAttempts int) (string, bool, error) { // Figure out if we should display a different string for this alias displayAlias := alias if val, ok := br.aliasMap[alias]; ok { displayAlias = val } // If typing on the terminal, we do not want the terminal to echo the // password that is typed (so it doesn't display) if term.IsTerminal(os.Stdin.Fd()) { state, err := term.SaveState(os.Stdin.Fd()) if err != nil { return "", false, err } term.DisableEcho(os.Stdin.Fd(), state) defer term.RestoreTerminal(os.Stdin.Fd(), state) } indexOfLastSeparator := strings.LastIndex(keyName, string(filepath.Separator)) if indexOfLastSeparator == -1 { indexOfLastSeparator = 0 } var shortName string if len(keyName) > indexOfLastSeparator+idBytesToDisplay { if indexOfLastSeparator > 0 { keyNamePrefix := keyName[:indexOfLastSeparator] keyNameID := keyName[indexOfLastSeparator+1 : indexOfLastSeparator+idBytesToDisplay+1] shortName = keyNameID + " (" + keyNamePrefix + ")" } else { shortName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay] } } withID := fmt.Sprintf(" with ID %s", shortName) if shortName == "" { withID = "" } switch { case createNew: fmt.Fprintf(br.out, "Enter passphrase for new %s key%s: ", displayAlias, withID) case displayAlias == "yubikey": fmt.Fprintf(br.out, "Enter the %s for the attached Yubikey: ", keyName) default: fmt.Fprintf(br.out, "Enter passphrase for %s key%s: ", displayAlias, withID) } stdin := bufio.NewReader(br.in) passphrase, err := stdin.ReadBytes('\n') fmt.Fprintln(br.out) if err != nil { return "", false, err } retPass := strings.TrimSpace(string(passphrase)) if createNew { err = br.verifyAndConfirmPassword(stdin, retPass, displayAlias, withID) if err != nil { return "", false, err } } br.cachePassword(alias, retPass) return retPass, false, nil }
// Run executes a validated remote execution against a pod. func (p *ExecOptions) Run() error { pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { return fmt.Errorf("pod %s is not running and cannot execute commands; current phase is %s", p.PodName, pod.Status.Phase) } containerName := p.ContainerName if len(containerName) == 0 { glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name) containerName = pod.Spec.Containers[0].Name } // TODO: refactor with terminal helpers from the edit utility once that is merged var stdin io.Reader tty := p.TTY if p.Stdin { stdin = p.In if tty { if file, ok := stdin.(*os.File); ok { inFd := file.Fd() if term.IsTerminal(inFd) { oldState, err := term.SetRawTerminal(inFd) if err != nil { glog.Fatal(err) } // this handles a clean exit, where the command finished defer term.RestoreTerminal(inFd, oldState) // SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens // for SIGINT and restores the terminal before exiting) // this handles SIGTERM sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) go func() { <-sigChan term.RestoreTerminal(inFd, oldState) os.Exit(0) }() } else { fmt.Fprintln(p.Err, "STDIN is not a terminal") } } else { tty = false fmt.Fprintln(p.Err, "Unable to use a TTY - input is not the right kind of file") } } } // TODO: consider abstracting into a client invocation or client helper req := p.Client.RESTClient.Post(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("exec"). Param("container", containerName) postErr := p.Executor.Execute(req, p.Config, p.Command, stdin, p.Out, p.Err, tty) // if we don't have an error, return. If we did get an error, try a GET because v3.0.0 shipped with exec running as a GET. if postErr == nil { return nil } // only try the get if the error is either a forbidden or method not supported, otherwise trying with a GET probably won't help if !apierrors.IsForbidden(postErr) && !apierrors.IsMethodNotSupported(postErr) { return postErr } getReq := p.Client.RESTClient.Get(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("exec"). Param("container", containerName) getErr := p.Executor.Execute(getReq, p.Config, p.Command, stdin, p.Out, p.Err, tty) if getErr == nil { return nil } // if we got a getErr, return the postErr because it's more likely to be correct. GET is legacy return postErr }
func runExport(args *docopt.Args, client controller.Client) error { var dest io.Writer = os.Stdout if filename := args.String["--file"]; filename != "" { f, err := os.Create(filename) if err != nil { return fmt.Errorf("error creating export file: %s", err) } defer f.Close() dest = f } app, err := client.GetApp(mustApp()) if err != nil { return fmt.Errorf("error getting app: %s", err) } var bar backup.ProgressBar if !args.Bool["--quiet"] && term.IsTerminal(os.Stderr.Fd()) { b := pb.New(0) b.SetUnits(pb.U_BYTES) b.ShowBar = false b.ShowSpeed = true b.Output = os.Stderr b.Start() defer b.Finish() bar = b } tw := backup.NewTarWriter(app.Name, dest, bar) defer tw.Close() if err := tw.WriteJSON("app.json", app); err != nil { return fmt.Errorf("error exporting app: %s", err) } routes, err := client.RouteList(mustApp()) if err != nil { return fmt.Errorf("error getting routes: %s", err) } if err := tw.WriteJSON("routes.json", routes); err != nil { return fmt.Errorf("error exporting routes: %s", err) } release, err := client.GetAppRelease(mustApp()) if err == controller.ErrNotFound { // if the app has no release then there is nothing more to export return nil } else if err != nil { return fmt.Errorf("error retrieving app: %s", err) } else if err == nil { // Do not allow the exporting of passwords. delete(release.Env, "REDIS_PASSWORD") if err := tw.WriteJSON("release.json", release); err != nil { return fmt.Errorf("error exporting release: %s", err) } } var artifact *ct.Artifact if artifactID := release.ImageArtifactID(); artifactID != "" { artifact, err = client.GetArtifact(artifactID) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving artifact: %s", err) } else if err == nil { if err := tw.WriteJSON("artifact.json", artifact); err != nil { return fmt.Errorf("error exporting artifact: %s", err) } } } formation, err := client.GetFormation(mustApp(), release.ID) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving formation: %s", err) } else if err == nil { if err := tw.WriteJSON("formation.json", formation); err != nil { return fmt.Errorf("error exporting formation: %s", err) } } // if the release was deployed via docker-receive, pull the docker // image and add it to the export using "docker save" if release.IsDockerReceiveDeploy() && artifact != nil { cluster, err := getCluster() if err != nil { return err } host, err := cluster.DockerPushHost() if err != nil { return err } // the artifact will have an internal discoverd URL which will // not work if the Docker daemon is outside the cluster, so // generate a reference using the configured DockerPushURL repo := artifact.Meta["docker-receive.repository"] digest := artifact.Meta["docker-receive.digest"] ref := fmt.Sprintf("%s/%s@%s", host, repo, digest) // pull the Docker image cmd := exec.Command("docker", "pull", ref) log.Printf("flynn: pulling Docker image with %q", strings.Join(cmd.Args, " ")) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return err } // give the image an explicit, random tag so that "docker save" // will export an image that we can reference on import (just // using the digest is not enough as "docker inspect" only // works with tags) tag := fmt.Sprintf("%s:flynn-export-%s", repo, random.String(8)) if out, err := exec.Command("docker", "tag", "--force", ref, tag).CombinedOutput(); err != nil { return fmt.Errorf("error tagging docker image: %s: %q", err, out) } defer exec.Command("docker", "rmi", tag).Run() if err := dockerSave(tag, tw, bar); err != nil { return fmt.Errorf("error exporting docker image: %s", err) } // add the tag to the backup so we know how to reference the // image once it has been imported config := struct { Tag string `json:"tag"` }{tag} if err := tw.WriteJSON("docker-image.json", &config); err != nil { return fmt.Errorf("error exporting docker image: %s", err) } } // expect releases deployed via git to have a slug as their first file // artifact, and legacy releases to have SLUG_URL set var slugURL string if release.IsGitDeploy() && len(release.FileArtifactIDs()) > 0 { slugArtifact, err := client.GetArtifact(release.FileArtifactIDs()[0]) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving slug artifact: %s", err) } else if err == nil { slugURL = slugArtifact.URI } } else if u, ok := release.Env["SLUG_URL"]; ok { slugURL = u } if slugURL != "" { reqR, reqW := io.Pipe() config := runConfig{ App: mustApp(), Release: release.ID, DisableLog: true, Args: []string{"curl", "--include", "--location", "--raw", slugURL}, Stdout: reqW, Stderr: ioutil.Discard, } if bar != nil { config.Stdout = io.MultiWriter(config.Stdout, bar) } go func() { if err := runJob(client, config); err != nil { shutdown.Fatalf("error retrieving slug: %s", err) } }() req := bufio.NewReader(reqR) var res *http.Response maxRedirects := 5 for i := 0; i < maxRedirects; i++ { res, err = http.ReadResponse(req, nil) if err != nil { return fmt.Errorf("error reading slug response: %s", err) } if res.StatusCode != http.StatusFound { break } } if res.StatusCode != http.StatusOK { return fmt.Errorf("unexpected status getting slug: %d", res.StatusCode) } length, err := strconv.Atoi(res.Header.Get("Content-Length")) if err != nil { return fmt.Errorf("slug has missing or malformed Content-Length") } if err := tw.WriteHeader("slug.tar.gz", length); err != nil { return fmt.Errorf("error writing slug header: %s", err) } if _, err := io.Copy(tw, res.Body); err != nil { return fmt.Errorf("error writing slug: %s", err) } res.Body.Close() } if pgConfig, err := getAppPgRunConfig(client); err == nil { configPgDump(pgConfig) if err := tw.WriteCommandOutput(client, "postgres.dump", pgConfig.App, &ct.NewJob{ ReleaseID: pgConfig.Release, Args: pgConfig.Args, Env: pgConfig.Env, DisableLog: pgConfig.DisableLog, }); err != nil { return fmt.Errorf("error creating postgres dump: %s", err) } } if mysqlConfig, err := getAppMysqlRunConfig(client); err == nil { configMysqlDump(mysqlConfig) if err := tw.WriteCommandOutput(client, "mysql.dump", mysqlConfig.App, &ct.NewJob{ ReleaseID: mysqlConfig.Release, Args: mysqlConfig.Args, Env: mysqlConfig.Env, DisableLog: mysqlConfig.DisableLog, }); err != nil { return fmt.Errorf("error creating mysql dump: %s", err) } } return nil }
func runImport(args *docopt.Args, client controller.Client) error { var src io.Reader = os.Stdin if filename := args.String["--file"]; filename != "" { f, err := os.Open(filename) if err != nil { return fmt.Errorf("error opening export file: %s", err) } defer f.Close() src = f } tr := tar.NewReader(src) var ( app *ct.App release *ct.Release imageArtifact *ct.Artifact formation *ct.Formation routes []router.Route slug io.Reader dockerImage struct { config struct { Tag string `json:"tag"` } archive io.Reader } pgDump io.Reader mysqlDump io.Reader uploadSize int64 ) numResources := 0 numRoutes := 1 for { header, err := tr.Next() if err == io.EOF { break } else if err != nil { return fmt.Errorf("error reading export tar: %s", err) } switch path.Base(header.Name) { case "app.json": app = &ct.App{} if err := json.NewDecoder(tr).Decode(app); err != nil { return fmt.Errorf("error decoding app: %s", err) } app.ID = "" case "release.json": release = &ct.Release{} if err := json.NewDecoder(tr).Decode(release); err != nil { return fmt.Errorf("error decoding release: %s", err) } release.ID = "" release.ArtifactIDs = nil case "artifact.json": imageArtifact = &ct.Artifact{} if err := json.NewDecoder(tr).Decode(imageArtifact); err != nil { return fmt.Errorf("error decoding image artifact: %s", err) } imageArtifact.ID = "" case "formation.json": formation = &ct.Formation{} if err := json.NewDecoder(tr).Decode(formation); err != nil { return fmt.Errorf("error decoding formation: %s", err) } formation.AppID = "" formation.ReleaseID = "" case "routes.json": if err := json.NewDecoder(tr).Decode(&routes); err != nil { return fmt.Errorf("error decoding routes: %s", err) } for _, route := range routes { route.ID = "" route.ParentRef = "" } case "slug.tar.gz": f, err := ioutil.TempFile("", "slug.tar.gz") if err != nil { return fmt.Errorf("error creating slug tempfile: %s", err) } defer f.Close() defer os.Remove(f.Name()) if _, err := io.Copy(f, tr); err != nil { return fmt.Errorf("error reading slug: %s", err) } if _, err := f.Seek(0, os.SEEK_SET); err != nil { return fmt.Errorf("error seeking slug tempfile: %s", err) } slug = f uploadSize += header.Size case "docker-image.json": if err := json.NewDecoder(tr).Decode(&dockerImage.config); err != nil { return fmt.Errorf("error decoding docker image json: %s", err) } case "docker-image.tar": f, err := ioutil.TempFile("", "docker-image.tar") if err != nil { return fmt.Errorf("error creating docker image tempfile: %s", err) } defer f.Close() defer os.Remove(f.Name()) if _, err := io.Copy(f, tr); err != nil { return fmt.Errorf("error reading docker image: %s", err) } if _, err := f.Seek(0, os.SEEK_SET); err != nil { return fmt.Errorf("error seeking docker image tempfile: %s", err) } dockerImage.archive = f uploadSize += header.Size case "postgres.dump": f, err := ioutil.TempFile("", "postgres.dump") if err != nil { return fmt.Errorf("error creating db tempfile: %s", err) } defer f.Close() defer os.Remove(f.Name()) if _, err := io.Copy(f, tr); err != nil { return fmt.Errorf("error reading db dump: %s", err) } if _, err := f.Seek(0, os.SEEK_SET); err != nil { return fmt.Errorf("error seeking db tempfile: %s", err) } pgDump = f uploadSize += header.Size case "mysql.dump": f, err := ioutil.TempFile("", "mysql.dump") if err != nil { return fmt.Errorf("error creating db tempfile: %s", err) } defer f.Close() defer os.Remove(f.Name()) if _, err := io.Copy(f, tr); err != nil { return fmt.Errorf("error reading db dump: %s", err) } if _, err := f.Seek(0, os.SEEK_SET); err != nil { return fmt.Errorf("error seeking db tempfile: %s", err) } mysqlDump = f uploadSize += header.Size } } if app == nil { return fmt.Errorf("missing app.json") } oldName := app.Name if name := args.String["--name"]; name != "" { app.Name = name } if err := client.CreateApp(app); err != nil { return fmt.Errorf("error creating app: %s", err) } var bar *pb.ProgressBar if !args.Bool["--quiet"] && uploadSize > 0 && term.IsTerminal(os.Stderr.Fd()) { bar = pb.New(0) bar.SetUnits(pb.U_BYTES) bar.Total = uploadSize bar.ShowSpeed = true bar.Output = os.Stderr bar.Start() defer bar.Finish() } if pgDump != nil && release != nil { res, err := client.ProvisionResource(&ct.ResourceReq{ ProviderID: "postgres", Apps: []string{app.ID}, }) if err != nil { return fmt.Errorf("error provisioning postgres resource: %s", err) } numResources++ if release.Env == nil { release.Env = make(map[string]string, len(res.Env)) } for k, v := range res.Env { release.Env[k] = v } config, err := getPgRunConfig(client, app.ID, release) if err != nil { return fmt.Errorf("error getting postgres config: %s", err) } config.Stdin = pgDump if bar != nil { config.Stdin = bar.NewProxyReader(config.Stdin) } config.Exit = false if err := pgRestore(client, config); err != nil { return fmt.Errorf("error restoring postgres database: %s", err) } } if mysqlDump != nil && release != nil { res, err := client.ProvisionResource(&ct.ResourceReq{ ProviderID: "mysql", Apps: []string{app.ID}, }) if err != nil { return fmt.Errorf("error provisioning mysql resource: %s", err) } numResources++ if release.Env == nil { release.Env = make(map[string]string, len(res.Env)) } for k, v := range res.Env { release.Env[k] = v } config, err := getMysqlRunConfig(client, app.ID, release) if err != nil { return fmt.Errorf("error getting mysql config: %s", err) } config.Stdin = mysqlDump if bar != nil { config.Stdin = bar.NewProxyReader(config.Stdin) } config.Exit = false if err := mysqlRestore(client, config); err != nil { return fmt.Errorf("error restoring mysql database: %s", err) } } if release != nil && release.Env["FLYNN_REDIS"] != "" { res, err := client.ProvisionResource(&ct.ResourceReq{ ProviderID: "redis", Apps: []string{app.ID}, }) if err != nil { return fmt.Errorf("error provisioning redis resource: %s", err) } numResources++ if release.Env == nil { release.Env = make(map[string]string, len(res.Env)) } for k, v := range res.Env { release.Env[k] = v } } uploadSlug := release != nil && imageArtifact != nil && slug != nil if uploadSlug { // Use current slugrunner as the artifact gitreceiveRelease, err := client.GetAppRelease("gitreceive") if err != nil { return fmt.Errorf("unable to retrieve gitreceive release: %s", err) } if id, ok := gitreceiveRelease.Env["SLUGRUNNER_IMAGE_ID"]; ok { imageArtifact, err = client.GetArtifact(id) if err != nil { return fmt.Errorf("unable to get slugrunner image artifact: %s", err) } } else if uri, ok := gitreceiveRelease.Env["SLUGRUNNER_IMAGE_URI"]; ok { imageArtifact = &ct.Artifact{ Type: host.ArtifactTypeDocker, URI: uri, } } else { return fmt.Errorf("gitreceive env missing slug runner image") } } if dockerImage.config.Tag != "" && dockerImage.archive != nil { // load the docker image into the Docker daemon cmd := exec.Command("docker", "load") cmd.Stdin = dockerImage.archive if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("error running docker load: %s: %q", err, out) } // use the tag from the config (which will now be applied to // the loaded image) to push the image to docker-receive cluster, err := getCluster() if err != nil { return err } host, err := cluster.DockerPushHost() if err != nil { return err } tag := fmt.Sprintf("%s/%s:latest", host, app.Name) if out, err := exec.Command("docker", "tag", "--force", dockerImage.config.Tag, tag).CombinedOutput(); err != nil { return fmt.Errorf("error tagging docker image: %s: %q", err, out) } artifact, err := dockerPush(client, app.Name, tag) if err != nil { return fmt.Errorf("error pushing docker image: %s", err) } release.ArtifactIDs = []string{artifact.ID} } else if imageArtifact != nil { if imageArtifact.ID == "" { if err := client.CreateArtifact(imageArtifact); err != nil { return fmt.Errorf("error creating image artifact: %s", err) } } release.ArtifactIDs = []string{imageArtifact.ID} } if release != nil { for t, proc := range release.Processes { for i, port := range proc.Ports { if port.Service != nil && strings.HasPrefix(port.Service.Name, oldName) { proc.Ports[i].Service.Name = strings.Replace(port.Service.Name, oldName, app.Name, 1) } } release.Processes[t] = proc } if err := client.CreateRelease(release); err != nil { return fmt.Errorf("error creating release: %s", err) } if err := client.SetAppRelease(app.ID, release.ID); err != nil { return fmt.Errorf("error setting app release: %s", err) } } if uploadSlug { slugURI := fmt.Sprintf("http://blobstore.discoverd/%s/slug.tgz", random.UUID()) config := runConfig{ App: app.ID, Release: release.ID, DisableLog: true, Args: []string{"curl", "--request", "PUT", "--upload-file", "-", slugURI}, Stdin: slug, Stdout: ioutil.Discard, Stderr: ioutil.Discard, } if bar != nil { config.Stdin = bar.NewProxyReader(config.Stdin) } if err := runJob(client, config); err != nil { return fmt.Errorf("error uploading slug: %s", err) } slugArtifact := &ct.Artifact{ Type: host.ArtifactTypeFile, URI: slugURI, } if err := client.CreateArtifact(slugArtifact); err != nil { return fmt.Errorf("error creating slug artifact: %s", err) } release.ID = "" release.ArtifactIDs = append(release.ArtifactIDs, slugArtifact.ID) if release.Meta == nil { release.Meta = make(map[string]string, 1) } release.Meta["git"] = "true" if err := client.CreateRelease(release); err != nil { return fmt.Errorf("error creating release: %s", err) } if err := client.SetAppRelease(app.ID, release.ID); err != nil { return fmt.Errorf("error setting app release: %s", err) } } if formation != nil && release != nil { formation.ReleaseID = release.ID formation.AppID = app.ID if err := client.PutFormation(formation); err != nil { return fmt.Errorf("error creating formation: %s", err) } } if args.Bool["--routes"] { for _, route := range routes { if err := client.CreateRoute(app.ID, &route); err != nil { if e, ok := err.(hh.JSONError); ok && e.Code == hh.ConflictErrorCode { // If the cluster domain matches then the default route // exported will conflict with the one created automatically. continue } return fmt.Errorf("error creating route: %s", err) } numRoutes++ } } fmt.Printf("Imported %s (added %d routes, provisioned %d resources)\n", app.Name, numRoutes, numResources) return nil }
// PromptRetrieverWithInOut returns a new Retriever which will provide a // prompt using the given in and out readers. The passphrase will be cached // such that subsequent prompts will produce the same passphrase. // aliasMap can be used to specify display names for TUF key aliases. If aliasMap // is nil, a sensible default will be used. func PromptRetrieverWithInOut(in io.Reader, out io.Writer, aliasMap map[string]string) Retriever { userEnteredTargetsSnapshotsPass := false targetsSnapshotsPass := "" userEnteredRootsPass := false rootsPass := "" return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) { if alias == tufRootAlias && createNew && numAttempts == 0 { fmt.Fprintln(out, tufRootKeyGenerationWarning) } if numAttempts > 0 { if !createNew { fmt.Fprintln(out, "Passphrase incorrect. Please retry.") } } // Figure out if we should display a different string for this alias displayAlias := alias if aliasMap != nil { if val, ok := aliasMap[alias]; ok { displayAlias = val } } // First, check if we have a password cached for this alias. if numAttempts == 0 { if userEnteredTargetsSnapshotsPass && (alias == tufSnapshotAlias || alias == tufTargetsAlias) { return targetsSnapshotsPass, false, nil } if userEnteredRootsPass && (alias == "root") { return rootsPass, false, nil } } if numAttempts > 3 && !createNew { return "", true, ErrTooManyAttempts } // If typing on the terminal, we do not want the terminal to echo the // password that is typed (so it doesn't display) if term.IsTerminal(0) { state, err := term.SaveState(0) if err != nil { return "", false, err } term.DisableEcho(0, state) defer term.RestoreTerminal(0, state) } stdin := bufio.NewReader(in) indexOfLastSeparator := strings.LastIndex(keyName, string(filepath.Separator)) if indexOfLastSeparator == -1 { indexOfLastSeparator = 0 } var shortName string if len(keyName) > indexOfLastSeparator+idBytesToDisplay { if indexOfLastSeparator > 0 { keyNamePrefix := keyName[:indexOfLastSeparator] keyNameID := keyName[indexOfLastSeparator+1 : indexOfLastSeparator+idBytesToDisplay+1] shortName = keyNameID + " (" + keyNamePrefix + ")" } else { shortName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay] } } withID := fmt.Sprintf(" with ID %s", shortName) if shortName == "" { withID = "" } if createNew { fmt.Fprintf(out, "Enter passphrase for new %s key%s: ", displayAlias, withID) } else if displayAlias == "yubikey" { fmt.Fprintf(out, "Enter the %s for the attached Yubikey: ", keyName) } else { fmt.Fprintf(out, "Enter passphrase for %s key%s: ", displayAlias, withID) } passphrase, err := stdin.ReadBytes('\n') fmt.Fprintln(out) if err != nil { return "", false, err } retPass := strings.TrimSpace(string(passphrase)) if !createNew { if alias == tufSnapshotAlias || alias == tufTargetsAlias { userEnteredTargetsSnapshotsPass = true targetsSnapshotsPass = retPass } if alias == tufRootAlias { userEnteredRootsPass = true rootsPass = retPass } return retPass, false, nil } if len(retPass) < 8 { fmt.Fprintln(out, "Passphrase is too short. Please use a password manager to generate and store a good random passphrase.") return "", false, ErrTooShort } fmt.Fprintf(out, "Repeat passphrase for new %s key%s: ", displayAlias, withID) confirmation, err := stdin.ReadBytes('\n') fmt.Fprintln(out) if err != nil { return "", false, err } confirmationStr := strings.TrimSpace(string(confirmation)) if retPass != confirmationStr { fmt.Fprintln(out, "Passphrases do not match. Please retry.") return "", false, ErrDontMatch } if alias == tufSnapshotAlias || alias == tufTargetsAlias { userEnteredTargetsSnapshotsPass = true targetsSnapshotsPass = retPass } if alias == tufRootAlias { userEnteredRootsPass = true rootsPass = retPass } return retPass, false, nil } }
func runRun(args *docopt.Args, client *cluster.Client) error { cmd := exec.Cmd{ ImageArtifact: exec.DockerImage(args.String["<image>"]), Job: &host.Job{ Config: host.ContainerConfig{ Args: append([]string{args.String["<command>"]}, args.All["<argument>"].([]string)...), TTY: term.IsTerminal(os.Stdin.Fd()) && term.IsTerminal(os.Stdout.Fd()), Stdin: true, DisableLog: 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 (t *terminalHelper) IsTerminal(fd uintptr) bool { return term.IsTerminal(fd) }
func runJob(client controller.Client, config runConfig) error { req := &ct.NewJob{ Args: config.Args, TTY: config.Stdin == nil && config.Stdout == nil && term.IsTerminal(os.Stdin.Fd()) && term.IsTerminal(os.Stdout.Fd()) && !config.Detached, ReleaseID: config.Release, Env: config.Env, ReleaseEnv: config.ReleaseEnv, DisableLog: config.DisableLog, } // ensure slug apps from old clusters use /runner/init release, err := client.GetRelease(req.ReleaseID) if err != nil { return err } if release.IsGitDeploy() && (len(req.Args) == 0 || req.Args[0] != "/runner/init") { req.Args = append([]string{"/runner/init"}, req.Args...) } // set deprecated Entrypoint and Cmd for old clusters if len(req.Args) > 0 { req.DeprecatedEntrypoint = []string{req.Args[0]} } if len(req.Args) > 1 { req.DeprecatedCmd = req.Args[1:] } 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 }
// Run executes a validated remote execution against a pod. func (p *AttachOptions) Run() error { pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { return fmt.Errorf("pod %s is not running and cannot be attached to; current phase is %s", p.PodName, pod.Status.Phase) } var stdin io.Reader tty := p.TTY containerToAttach := p.GetContainer(pod) if tty && !containerToAttach.TTY { tty = false fmt.Fprintf(p.Err, "Unable to use a TTY - container %s doesn't allocate one\n", containerToAttach.Name) } // TODO: refactor with terminal helpers from the edit utility once that is merged if p.Stdin { stdin = p.In if tty { if file, ok := stdin.(*os.File); ok { inFd := file.Fd() if term.IsTerminal(inFd) { oldState, err := term.SetRawTerminal(inFd) if err != nil { glog.Fatal(err) } fmt.Fprintln(p.Out, "\nHit enter for command prompt") // this handles a clean exit, where the command finished defer term.RestoreTerminal(inFd, oldState) // SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens // for SIGINT and restores the terminal before exiting) // this handles SIGTERM sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) go func() { <-sigChan term.RestoreTerminal(inFd, oldState) os.Exit(0) }() } else { fmt.Fprintln(p.Err, "STDIN is not a terminal") } } else { tty = false fmt.Fprintln(p.Err, "Unable to use a TTY - input is not the right kind of file") } } } // TODO: consider abstracting into a client invocation or client helper req := p.Client.RESTClient.Post(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("attach") req.VersionedParams(&api.PodAttachOptions{ Container: containerToAttach.Name, Stdin: stdin != nil, Stdout: p.Out != nil, Stderr: p.Err != nil, TTY: tty, }, api.ParameterCodec) err = p.Attach.Attach("POST", req.URL(), p.Config, stdin, p.Out, p.Err, tty) if err != nil { return err } if p.Stdin && tty && pod.Spec.RestartPolicy == api.RestartPolicyAlways { fmt.Fprintf(p.Out, "Session ended, resume using 'kubectl attach %s -c %s -i -t' command when the pod is running\n", pod.Name, containerToAttach.Name) } return nil }
func (c *Client) stream(method, path string, in io.Reader, out io.Writer, headers http.Header) error { if (method == "POST" || method == "PUT") && in == nil { in = bytes.NewReader(nil) } // setup the request req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in) if err != nil { return err } // set default headers req.Header = headers req.Header.Set("User-Agent", "Docker-Client/0.6.4") req.Header.Set("Content-Type", "plain/text") // dial the host server req.URL.Host = c.addr req.URL.Scheme = "http" if c.tls != nil { req.URL.Scheme = "https" } resp, err := c.HTTPClient().Do(req) if err != nil { return err } // make sure we defer close the body defer resp.Body.Close() // Check for an http error status (ie not 200 StatusOK) switch resp.StatusCode { case 500: return ErrInternalServer case 404: return ErrNotFound case 403: return ErrForbidden case 401: return ErrNotAuthorized case 400: return ErrBadRequest } // If no output we exit now with no errors if out == nil { io.Copy(ioutil.Discard, resp.Body) return nil } // copy the output stream to the writer if resp.Header.Get("Content-Type") == "application/json" { var terminalFd = os.Stdin.Fd() var isTerminal = term.IsTerminal(terminalFd) // it may not make sense to put this code here, but it works for // us at the moment, and I don't feel like refactoring return jsonmessage.DisplayJSONMessagesStream(resp.Body, out, terminalFd, isTerminal) } // otherwise plain text if _, err := io.Copy(out, resp.Body); err != nil { return err } return nil }
func RunExec(f *cmdutil.Factory, cmd *cobra.Command, cmdIn io.Reader, cmdOut, cmdErr io.Writer, p *execParams, args []string, re remoteExecutor) error { if len(p.podName) == 0 { return cmdutil.UsageError(cmd, "POD is required for exec") } if len(args) < 1 { return cmdutil.UsageError(cmd, "COMMAND is required for exec") } namespace, err := f.DefaultNamespace() if err != nil { return err } client, err := f.Client() if err != nil { return err } pod, err := client.Pods(namespace).Get(p.podName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { glog.Fatalf("Unable to execute command because pod is not running. Current status=%v", pod.Status.Phase) } containerName := p.containerName if len(containerName) == 0 { containerName = pod.Spec.Containers[0].Name } var stdin io.Reader tty := p.tty if p.stdin { stdin = cmdIn if tty { if file, ok := cmdIn.(*os.File); ok { inFd := file.Fd() if term.IsTerminal(inFd) { oldState, err := term.SetRawTerminal(inFd) if err != nil { glog.Fatal(err) } // this handles a clean exit, where the command finished defer term.RestoreTerminal(inFd, oldState) // SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens // for SIGINT and restores the terminal before exiting) // this handles SIGTERM sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) go func() { <-sigChan term.RestoreTerminal(inFd, oldState) os.Exit(0) }() } else { glog.Warning("Stdin is not a terminal") } } else { tty = false glog.Warning("Unable to use a TTY") } } } config, err := f.ClientConfig() if err != nil { return err } req := client.RESTClient.Get(). Resource("pods"). Name(pod.Name). Namespace(namespace). SubResource("exec"). Param("container", containerName) return re.Execute(req, config, args, stdin, cmdOut, cmdErr, tty) }