diff --git a/frontend/src/libcfcontainer/cuttlefish_container.go b/frontend/src/libcfcontainer/cuttlefish_container.go index c44bba9c06..727dc6e589 100644 --- a/frontend/src/libcfcontainer/cuttlefish_container.go +++ b/frontend/src/libcfcontainer/cuttlefish_container.go @@ -15,6 +15,7 @@ package libcfcontainer import ( + "bytes" "context" "fmt" "io" @@ -27,6 +28,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" + "github.com/docker/docker/pkg/stdcopy" ) type CuttlefishContainerManager interface { @@ -37,7 +39,7 @@ type CuttlefishContainerManager interface { // Create adn start a container instance CreateAndStartContainer(ctx context.Context, additionalConfig *container.Config, additionalHostConfig *container.HostConfig, name string) (string, error) // Execute a command on a running container instance - ExecOnContainer(ctx context.Context, ctr string, cmd []string) error + ExecOnContainer(ctx context.Context, ctr string, cmd []string) (string, error) } type CuttlefishContainerManagerOpts struct { @@ -147,21 +149,21 @@ func (m *CuttlefishContainerManagerImpl) CreateAndStartContainer(ctx context.Con return createRes.ID, nil } -func (m *CuttlefishContainerManagerImpl) ExecOnContainer(ctx context.Context, ctr string, cmd []string) error { +func (m *CuttlefishContainerManagerImpl) ExecOnContainer(ctx context.Context, ctr string, cmd []string) (string, error) { execConfig := container.ExecOptions{ AttachStderr: true, AttachStdin: true, AttachStdout: true, Cmd: cmd, - Tty: true, + Tty: false, } createRes, err := m.cli.ContainerExecCreate(ctx, ctr, execConfig) if err != nil { - return fmt.Errorf("failed to create container execution %q: %w", strings.Join(cmd, " "), err) + return "", fmt.Errorf("failed to create container execution %q: %w", strings.Join(cmd, " "), err) } attachRes, err := m.cli.ContainerExecAttach(ctx, createRes.ID, container.ExecStartOptions{}) if err != nil { - return fmt.Errorf("failed to attach container execution %q: %w", strings.Join(cmd, " "), err) + return "", fmt.Errorf("failed to attach container execution %q: %w", strings.Join(cmd, " "), err) } waitCh := make(chan struct{}) go func() { @@ -170,20 +172,22 @@ func (m *CuttlefishContainerManagerImpl) ExecOnContainer(ctx context.Context, ct log.Printf("failed to propagate standard input: %v", err) } }() + var stdoutBuf bytes.Buffer go func() { defer close(waitCh) - if _, err := io.Copy(os.Stdout, attachRes.Reader); err != nil { + stdout := io.MultiWriter(os.Stdout, &stdoutBuf) + if _, err := stdcopy.StdCopy(stdout, os.Stderr, attachRes.Reader); err != nil { log.Printf("failed to propagate standard output: %v", err) } }() <-waitCh attachRes.Close() if result, err := m.cli.ContainerExecInspect(ctx, createRes.ID); err != nil { - return fmt.Errorf("failed to run command on the container: %w", err) + return "", fmt.Errorf("failed to run command on the container: %w", err) } else if result.ExitCode != 0 { - return fmt.Errorf("failed to run command on the container with exit code %d", result.ExitCode) + return "", fmt.Errorf("failed to run command on the container with exit code %d", result.ExitCode) } - return nil + return stdoutBuf.String(), nil } func RootlessPodmanSocketAddr() string { diff --git a/frontend/src/podcvd/main.go b/frontend/src/podcvd/main.go index ff2748ae4b..7d6c0c812d 100644 --- a/frontend/src/podcvd/main.go +++ b/frontend/src/podcvd/main.go @@ -17,11 +17,13 @@ package main import ( "context" "crypto/tls" + "encoding/json" "flag" "fmt" "log" "net/http" "os" + "os/exec" "time" "github.com/google/android-cuttlefish/frontend/src/libcfcontainer" @@ -102,6 +104,40 @@ func prepareCuttlefishHost(ccm libcfcontainer.CuttlefishContainerManager) (strin return id, nil } +func parseAdbPorts(stdout string) ([]int, error) { + type Instance struct { + AdbPort int `json:"adb_port"` + } + type InstanceGroup struct { + Instances []Instance `json:"instances"` + } + var instanceGroup InstanceGroup + if err := json.Unmarshal([]byte(stdout), &instanceGroup); err != nil { + return nil, err + } + var ports []int + for _, instance := range instanceGroup.Instances { + ports = append(ports, instance.AdbPort) + } + return ports, nil +} + +func establishAdbConnection(ports ...int) error { + adbBin, err := exec.LookPath("adb") + if err != nil { + return fmt.Errorf("failed to find adb: %w", err) + } + if err := exec.Command(adbBin, "start-server").Run(); err != nil { + return fmt.Errorf("failed to start server: %w", err) + } + for _, port := range ports { + if err := exec.Command(adbBin, "connect", fmt.Sprintf("localhost:%d", port)).Run(); err != nil { + return fmt.Errorf("failed to connect to Cuttlefish device: %w", err) + } + } + return nil +} + func main() { // Parse selector and driver options before the subcommand argument only. // TODO(seungjaeyoo): Handle selector/driver options properly for @@ -135,7 +171,16 @@ func main() { if err != nil { log.Fatal(err) } - if err := ccm.ExecOnContainer(context.Background(), id, append([]string{"cvd"}, os.Args[1:]...)); err != nil { + stdout, err := ccm.ExecOnContainer(context.Background(), id, append([]string{"cvd"}, os.Args[1:]...)) + if err != nil { + log.Fatal(err) + } + // TODO(seungjaeyoo): Establish ADB connection only when it's required. + ports, err := parseAdbPorts(stdout) + if err != nil { + log.Fatal(err) + } + if err := establishAdbConnection(ports...); err != nil { log.Fatal(err) } }