func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface{}) (err error) { // Build the RemoteCmd on this side so that it all pipes over // to the remote side. var cmd packer.RemoteCmd cmd.Command = args.Command if args.StdinAddress != "" { stdinC, err := net.Dial("tcp", args.StdinAddress) if err != nil { return err } cmd.Stdin = stdinC } if args.StdoutAddress != "" { stdoutC, err := net.Dial("tcp", args.StdoutAddress) if err != nil { return err } cmd.Stdout = stdoutC } if args.StderrAddress != "" { stderrC, err := net.Dial("tcp", args.StderrAddress) if err != nil { return err } cmd.Stderr = stderrC } // Connect to the response address so we can write our result to it // when ready. responseC, err := net.Dial("tcp", args.ResponseAddress) if err != nil { return err } responseWriter := gob.NewEncoder(responseC) // Start the actual command err = c.c.Start(&cmd) // Start a goroutine to spin and wait for the process to actual // exit. When it does, report it back to caller... go func() { defer responseC.Close() for !cmd.Exited { time.Sleep(50 * time.Millisecond) } responseWriter.Encode(&CommandFinished{cmd.ExitStatus}) }() return }
func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface{}) error { // Build the RemoteCmd on this side so that it all pipes over // to the remote side. var cmd packer.RemoteCmd cmd.Command = args.Command // Create a channel to signal we're done so that we can close // our stdin/stdout/stderr streams toClose := make([]io.Closer, 0) doneCh := make(chan struct{}) go func() { <-doneCh for _, conn := range toClose { defer conn.Close() } }() if args.StdinStreamId > 0 { conn, err := c.mux.Dial(args.StdinStreamId) if err != nil { close(doneCh) return NewBasicError(err) } toClose = append(toClose, conn) cmd.Stdin = conn } if args.StdoutStreamId > 0 { conn, err := c.mux.Dial(args.StdoutStreamId) if err != nil { close(doneCh) return NewBasicError(err) } toClose = append(toClose, conn) cmd.Stdout = conn } if args.StderrStreamId > 0 { conn, err := c.mux.Dial(args.StderrStreamId) if err != nil { close(doneCh) return NewBasicError(err) } toClose = append(toClose, conn) cmd.Stderr = conn } // Connect to the response address so we can write our result to it // when ready. responseC, err := c.mux.Dial(args.ResponseStreamId) if err != nil { close(doneCh) return NewBasicError(err) } responseWriter := gob.NewEncoder(responseC) // Start the actual command err = c.c.Start(&cmd) if err != nil { close(doneCh) return NewBasicError(err) } // Start a goroutine to spin and wait for the process to actual // exit. When it does, report it back to caller... go func() { defer close(doneCh) defer responseC.Close() cmd.Wait() log.Printf("[INFO] RPC endpoint: Communicator ended with: %d", cmd.ExitStatus) responseWriter.Encode(&CommandFinished{cmd.ExitStatus}) }() return nil }
func TestCommunicatorRPC(t *testing.T) { // Create the interface to test c := new(packer.MockCommunicator) // Start the server server := rpc.NewServer() RegisterCommunicator(server, c) address := serveSingleConn(server) // Create the client over RPC and run some methods to verify it works client, err := rpc.Dial("tcp", address) if err != nil { t.Fatalf("err: %s", err) } remote := Communicator(client) // The remote command we'll use stdin_r, stdin_w := io.Pipe() stdout_r, stdout_w := io.Pipe() stderr_r, stderr_w := io.Pipe() var cmd packer.RemoteCmd cmd.Command = "foo" cmd.Stdin = stdin_r cmd.Stdout = stdout_w cmd.Stderr = stderr_w // Send some data on stdout and stderr from the mock c.StartStdout = "outfoo\n" c.StartStderr = "errfoo\n" c.StartExitStatus = 42 // Test Start err = remote.Start(&cmd) if err != nil { t.Fatalf("err: %s", err) } // Test that we can read from stdout bufOut := bufio.NewReader(stdout_r) data, err := bufOut.ReadString('\n') if err != nil { t.Fatalf("err: %s", err) } if data != "outfoo\n" { t.Fatalf("bad data: %s", data) } // Test that we can read from stderr bufErr := bufio.NewReader(stderr_r) data, err = bufErr.ReadString('\n') if err != nil { t.Fatalf("err: %s", err) } if data != "errfoo\n" { t.Fatalf("bad data: %s", data) } // Test that we can write to stdin stdin_w.Write([]byte("info\n")) stdin_w.Close() cmd.Wait() if c.StartStdin != "info\n" { t.Fatalf("bad data: %s", data) } // Test that we can get the exit status properly if cmd.ExitStatus != 42 { t.Fatalf("bad exit: %d", cmd.ExitStatus) } // Test that we can upload things uploadR, uploadW := io.Pipe() go func() { defer uploadW.Close() uploadW.Write([]byte("uploadfoo\n")) }() err = remote.Upload("foo", uploadR) if err != nil { t.Fatalf("err: %s", err) } if !c.UploadCalled { t.Fatal("should have uploaded") } if c.UploadPath != "foo" { t.Fatalf("path: %s", c.UploadPath) } if c.UploadData != "uploadfoo\n" { t.Fatalf("bad: %s", c.UploadData) } // Test that we can upload directories dirDst := "foo" dirSrc := "bar" dirExcl := []string{"foo"} err = remote.UploadDir(dirDst, dirSrc, dirExcl) if err != nil { t.Fatalf("err: %s", err) } if c.UploadDirDst != dirDst { t.Fatalf("bad: %s", c.UploadDirDst) } if c.UploadDirSrc != dirSrc { t.Fatalf("bad: %s", c.UploadDirSrc) } if !reflect.DeepEqual(c.UploadDirExclude, dirExcl) { t.Fatalf("bad: %#v", c.UploadDirExclude) } // Test that we can download things downloadR, downloadW := io.Pipe() downloadDone := make(chan bool) var downloadData string var downloadErr error go func() { bufDownR := bufio.NewReader(downloadR) downloadData, downloadErr = bufDownR.ReadString('\n') downloadDone <- true }() c.DownloadData = "download\n" err = remote.Download("bar", downloadW) if err != nil { t.Fatalf("err: %s", err) } if !c.DownloadCalled { t.Fatal("download should be called") } if c.DownloadPath != "bar" { t.Fatalf("bad: %s", c.DownloadPath) } <-downloadDone if downloadErr != nil { t.Fatalf("err: %s", downloadErr) } if downloadData != "download\n" { t.Fatalf("bad: %s", downloadData) } }
func TestCommunicatorRPC(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) // Create the interface to test c := new(testCommunicator) // Start the server server := rpc.NewServer() RegisterCommunicator(server, c) address := serveSingleConn(server) // Create the client over RPC and run some methods to verify it works client, err := rpc.Dial("tcp", address) assert.Nil(err, "should be able to connect") remote := Communicator(client) // The remote command we'll use stdin_r, stdin_w := io.Pipe() stdout_r, stdout_w := io.Pipe() stderr_r, stderr_w := io.Pipe() var cmd packer.RemoteCmd cmd.Command = "foo" cmd.Stdin = stdin_r cmd.Stdout = stdout_w cmd.Stderr = stderr_w // Test Start err = remote.Start(&cmd) assert.Nil(err, "should not have an error") // Test that we can read from stdout c.startCmd.Stdout.Write([]byte("outfoo\n")) bufOut := bufio.NewReader(stdout_r) data, err := bufOut.ReadString('\n') assert.Nil(err, "should have no problem reading stdout") assert.Equal(data, "outfoo\n", "should be correct stdout") // Test that we can read from stderr c.startCmd.Stderr.Write([]byte("errfoo\n")) bufErr := bufio.NewReader(stderr_r) data, err = bufErr.ReadString('\n') assert.Nil(err, "should have no problem reading stderr") assert.Equal(data, "errfoo\n", "should be correct stderr") // Test that we can write to stdin stdin_w.Write([]byte("infoo\n")) bufIn := bufio.NewReader(c.startCmd.Stdin) data, err = bufIn.ReadString('\n') assert.Nil(err, "should have no problem reading stdin") assert.Equal(data, "infoo\n", "should be correct stdin") // Test that we can get the exit status properly c.startCmd.ExitStatus = 42 c.startCmd.Exited = true for i := 0; i < 5; i++ { if cmd.Exited { assert.Equal(cmd.ExitStatus, 42, "should have proper exit status") break } time.Sleep(50 * time.Millisecond) } assert.True(cmd.Exited, "should have exited") // Test that we can upload things uploadR, uploadW := io.Pipe() go uploadW.Write([]byte("uploadfoo\n")) err = remote.Upload("foo", uploadR) assert.Nil(err, "should not error") assert.True(c.uploadCalled, "should be called") assert.Equal(c.uploadPath, "foo", "should be correct path") assert.Equal(c.uploadData, "uploadfoo\n", "should have the proper data") // Test that we can download things downloadR, downloadW := io.Pipe() downloadDone := make(chan bool) var downloadData string var downloadErr error go func() { bufDownR := bufio.NewReader(downloadR) downloadData, downloadErr = bufDownR.ReadString('\n') downloadDone <- true }() err = remote.Download("bar", downloadW) assert.Nil(err, "should not error") assert.True(c.downloadCalled, "should have called download") assert.Equal(c.downloadPath, "bar", "should have correct download path") <-downloadDone assert.Nil(downloadErr, "should not error reading download data") assert.Equal(downloadData, "download\n", "should have the proper data") }
func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface{}) (err error) { // Build the RemoteCmd on this side so that it all pipes over // to the remote side. var cmd packer.RemoteCmd cmd.Command = args.Command toClose := make([]net.Conn, 0) if args.StdinAddress != "" { stdinC, err := tcpDial(args.StdinAddress) if err != nil { return err } toClose = append(toClose, stdinC) cmd.Stdin = stdinC } if args.StdoutAddress != "" { stdoutC, err := tcpDial(args.StdoutAddress) if err != nil { return err } toClose = append(toClose, stdoutC) cmd.Stdout = stdoutC } if args.StderrAddress != "" { stderrC, err := tcpDial(args.StderrAddress) if err != nil { return err } toClose = append(toClose, stderrC) cmd.Stderr = stderrC } // Connect to the response address so we can write our result to it // when ready. responseC, err := tcpDial(args.ResponseAddress) if err != nil { return err } responseWriter := gob.NewEncoder(responseC) // Start the actual command err = c.c.Start(&cmd) // Start a goroutine to spin and wait for the process to actual // exit. When it does, report it back to caller... go func() { defer responseC.Close() for _, conn := range toClose { defer conn.Close() } cmd.Wait() responseWriter.Encode(&CommandFinished{cmd.ExitStatus}) }() return }