func hijack(conn net.Conn, br *bufio.Reader) int { var in io.Reader term, err := term.Open(os.Stdin.Name()) if err == nil { err = term.SetRaw() if err != nil { log.Fatalln("failed to set raw:", term) } defer term.Restore() in = term } else { in = os.Stdin } encoder := json.NewEncoder(conn) decoder := json.NewDecoder(br) resized := make(chan os.Signal, 10) signal.Notify(resized, syscall.SIGWINCH) go func() { for { <-resized // TODO json race sendSize(encoder) } }() go io.Copy(&stdinWriter{encoder}, in) var exitStatus int for { var output atc.HijackOutput err := decoder.Decode(&output) if err != nil { break } if output.ExitStatus != nil { exitStatus = *output.ExitStatus } else if len(output.Error) > 0 { fmt.Fprintf(os.Stderr, "%s\n", ansi.Color(output.Error, "red+b")) exitStatus = 255 } else if len(output.Stdout) > 0 { os.Stdout.Write(output.Stdout) } else if len(output.Stderr) > 0 { os.Stderr.Write(output.Stderr) } } return exitStatus }
func main() { app := cli.NewApp() app.Name = "gaol" app.Usage = "a cli for garden" app.Version = "0.0.1" app.Author = "Chris Brown" app.Email = "*****@*****.**" app.EnableBashCompletion = true app.Flags = []cli.Flag{ cli.StringFlag{ Name: "target, t", Value: "localhost:7777", Usage: "server to which commands are sent", EnvVar: "GAOL_TARGET", }, } app.Commands = []cli.Command{ { Name: "ping", Usage: "check if the server is running", Action: func(c *cli.Context) { err := client(c).Ping() failIf(err) }, }, { Name: "create", Usage: "create a container", Flags: []cli.Flag{ cli.StringFlag{ Name: "handle, n", Usage: "name to give container", }, cli.StringFlag{ Name: "rootfs, r", Usage: "rootfs image with which to create the container", }, cli.StringSliceFlag{ Name: "env, e", Usage: "set environment variables", Value: &cli.StringSlice{}, }, cli.DurationFlag{ Name: "grace, g", Usage: "grace time (resetting ttl) of container", Value: 5 * time.Minute, }, cli.BoolFlag{ Name: "privileged, p", Usage: "privileged user in container is privileged in host", }, cli.StringSliceFlag{ Name: "bind-mount, m", Usage: "bind-mount host-path:container-path", Value: &cli.StringSlice{}, }, }, Action: func(c *cli.Context) { handle := c.String("handle") grace := c.Duration("grace") rootfs := c.String("rootfs") env := c.StringSlice("env") privileged := c.Bool("privileged") mounts := c.StringSlice("bind-mount") var bindMounts []garden.BindMount for _, pair := range mounts { segs := strings.SplitN(pair, ":", 2) if len(segs) != 2 { fail(fmt.Errorf("invalid bind-mount segment (must be host-path:container-path): %s", pair)) } bindMounts = append(bindMounts, garden.BindMount{ SrcPath: segs[0], DstPath: segs[1], Mode: garden.BindMountModeRW, Origin: garden.BindMountOriginHost, }) } container, err := client(c).Create(garden.ContainerSpec{ Handle: handle, GraceTime: grace, RootFSPath: rootfs, Privileged: privileged, Env: env, BindMounts: bindMounts, }) failIf(err) fmt.Println(container.Handle()) }, }, { Name: "destroy", Usage: "destroy a container", BashComplete: handleComplete, Action: func(c *cli.Context) { client := client(c) handles := c.Args() for _, handle := range handles { err := client.Destroy(handle) failIf(err) } }, }, { Name: "list", Usage: "get a list of running containers", Flags: []cli.Flag{ cli.StringSliceFlag{ Name: "properties, p", Usage: "filter by properties (name=val)", Value: &cli.StringSlice{}, }, cli.BoolFlag{ Name: "verbose, v", Usage: "print additional details about each container", }, cli.StringFlag{ Name: "separator", Usage: "separator to print between containers in verbose mode", Value: "\n", }, }, Action: func(c *cli.Context) { separator := c.String("separator") properties := garden.Properties{} for _, prop := range c.StringSlice("properties") { segs := strings.SplitN(prop, "=", 2) if len(segs) < 2 { fail(errors.New("malformed property pair (must be name=value)")) } properties[segs[0]] = segs[1] } containers, err := client(c).Containers(properties) failIf(err) verbose := c.Bool("verbose") for _, container := range containers { fmt.Println(container.Handle()) if verbose { props, _ := container.Properties() for k, v := range props { fmt.Printf(" %s=%s\n", k, v) } fmt.Print(separator) } } }, }, { Name: "run", Usage: "run a command in a container", Flags: []cli.Flag{ cli.BoolFlag{ Name: "attach, a", Usage: "attach to the process after it is started", }, cli.StringFlag{ Name: "dir, d", Usage: "current working directory of process", }, cli.StringFlag{ Name: "user, u", Usage: "user to run the process as", }, cli.StringFlag{ Name: "command, c", Usage: "the command to run", }, cli.StringSliceFlag{ Name: "env, e", Usage: "set environment variables", Value: &cli.StringSlice{}, }, }, BashComplete: handleComplete, Action: func(c *cli.Context) { attach := c.Bool("attach") dir := c.String("dir") user := c.String("user") command := c.String("command") env := c.StringSlice("env") handle := handle(c) container, err := client(c).Lookup(handle) failIf(err) var processIo garden.ProcessIO if attach { processIo = garden.ProcessIO{ Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, } } else { processIo = garden.ProcessIO{} } args, err := shellwords.Parse(command) failIf(err) process, err := container.Run(garden.ProcessSpec{ Path: args[0], Args: args[1:], Dir: dir, User: user, Env: env, }, processIo) failIf(err) if attach { status, err := process.Wait() failIf(err) os.Exit(status) } else { fmt.Println(process.ID()) } }, }, { Name: "attach", Usage: "attach to command running in the container", Flags: []cli.Flag{ cli.IntFlag{ Name: "pid, p", Usage: "process id to connect to", }, }, BashComplete: handleComplete, Action: func(c *cli.Context) { pid := uint32(c.Int("pid")) if pid == 0 { err := errors.New("must specify pid to attach to") failIf(err) } handle := handle(c) container, err := client(c).Lookup(handle) failIf(err) process, err := container.Attach(pid, garden.ProcessIO{ Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, }) failIf(err) _, err = process.Wait() failIf(err) }, }, { Name: "shell", Usage: "open a shell inside the running container", Flags: []cli.Flag{ cli.StringFlag{ Name: "user, u", Usage: "user to open shell as", }, }, BashComplete: handleComplete, Action: func(c *cli.Context) { container, err := client(c).Lookup(handle(c)) failIf(err) term, err := term.Open(os.Stdin.Name()) failIf(err) err = term.SetRaw() failIf(err) rows, cols, err := pty.Getsize(os.Stdin) failIf(err) process, err := container.Run(garden.ProcessSpec{ User: c.String("user"), Path: "/bin/sh", Args: []string{"-l"}, Env: []string{"TERM=" + os.Getenv("TERM")}, TTY: &garden.TTYSpec{ WindowSize: &garden.WindowSize{ Rows: rows, Columns: cols, }, }, }, garden.ProcessIO{ Stdin: term, Stdout: term, Stderr: term, }) if err != nil { term.Restore() failIf(err) } resized := make(chan os.Signal, 10) signal.Notify(resized, syscall.SIGWINCH) go func() { for { <-resized rows, cols, err := pty.Getsize(os.Stdin) if err == nil { process.SetTTY(garden.TTYSpec{ WindowSize: &garden.WindowSize{ Rows: rows, Columns: cols, }, }) } } }() process.Wait() term.Restore() }, }, { Name: "stream-in", Usage: "stream data into the container", Flags: []cli.Flag{ cli.StringFlag{ Name: "destination, d", Usage: "destination path in the container", }, }, BashComplete: handleComplete, Action: func(c *cli.Context) { handle := handle(c) dst := c.String("destination") if dst == "" { fail(errors.New("missing --destination flag")) } container, err := client(c).Lookup(handle) failIf(err) streamInSpec := garden.StreamInSpec{ Path: dst, TarStream: os.Stdin, } err = container.StreamIn(streamInSpec) failIf(err) }, }, { Name: "stream-out", Usage: "stream data out of the container", Flags: []cli.Flag{ cli.StringFlag{ Name: "source, s", Usage: "source path in the container", }, }, BashComplete: handleComplete, Action: func(c *cli.Context) { handle := handle(c) src := c.String("source") if src == "" { fail(errors.New("missing --source flag")) } container, err := client(c).Lookup(handle) failIf(err) streamOutSpec := garden.StreamOutSpec{Path: src} output, err := container.StreamOut(streamOutSpec) failIf(err) io.Copy(os.Stdout, output) }, }, { Name: "net-in", Usage: "map a port on the host to a port in the container", Flags: []cli.Flag{ cli.IntFlag{ Name: "port, p", Usage: "container port", }, }, BashComplete: handleComplete, Action: func(c *cli.Context) { target := c.GlobalString("target") requestedContainerPort := uint32(c.Int("port")) if target == "" { fail(errors.New("target must be set")) } handle := handle(c) container, err := client(c).Lookup(handle) failIf(err) hostPort, _, err := container.NetIn(0, requestedContainerPort) failIf(err) host, _, err := net.SplitHostPort(target) failIf(err) fmt.Println(net.JoinHostPort(host, fmt.Sprintf("%d", hostPort))) }, }, } app.Run(os.Args) }