github.com/jerryclinesmith/packer@v0.3.7/packer/rpc/communicator.go (about)

     1  package rpc
     2  
     3  import (
     4  	"encoding/gob"
     5  	"errors"
     6  	"github.com/mitchellh/packer/packer"
     7  	"io"
     8  	"log"
     9  	"net"
    10  	"net/rpc"
    11  )
    12  
    13  // An implementation of packer.Communicator where the communicator is actually
    14  // executed over an RPC connection.
    15  type communicator struct {
    16  	client *rpc.Client
    17  }
    18  
    19  // CommunicatorServer wraps a packer.Communicator implementation and makes
    20  // it exportable as part of a Golang RPC server.
    21  type CommunicatorServer struct {
    22  	c packer.Communicator
    23  }
    24  
    25  type CommandFinished struct {
    26  	ExitStatus int
    27  }
    28  
    29  type CommunicatorStartArgs struct {
    30  	Command         string
    31  	StdinAddress    string
    32  	StdoutAddress   string
    33  	StderrAddress   string
    34  	ResponseAddress string
    35  }
    36  
    37  type CommunicatorDownloadArgs struct {
    38  	Path          string
    39  	WriterAddress string
    40  }
    41  
    42  type CommunicatorUploadArgs struct {
    43  	Path          string
    44  	ReaderAddress string
    45  }
    46  
    47  type CommunicatorUploadDirArgs struct {
    48  	Dst     string
    49  	Src     string
    50  	Exclude []string
    51  }
    52  
    53  func Communicator(client *rpc.Client) *communicator {
    54  	return &communicator{client}
    55  }
    56  
    57  func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
    58  	var args CommunicatorStartArgs
    59  	args.Command = cmd.Command
    60  
    61  	if cmd.Stdin != nil {
    62  		stdinL := netListenerInRange(portRangeMin, portRangeMax)
    63  		args.StdinAddress = stdinL.Addr().String()
    64  		go serveSingleCopy("stdin", stdinL, nil, cmd.Stdin)
    65  	}
    66  
    67  	if cmd.Stdout != nil {
    68  		stdoutL := netListenerInRange(portRangeMin, portRangeMax)
    69  		args.StdoutAddress = stdoutL.Addr().String()
    70  		go serveSingleCopy("stdout", stdoutL, cmd.Stdout, nil)
    71  	}
    72  
    73  	if cmd.Stderr != nil {
    74  		stderrL := netListenerInRange(portRangeMin, portRangeMax)
    75  		args.StderrAddress = stderrL.Addr().String()
    76  		go serveSingleCopy("stderr", stderrL, cmd.Stderr, nil)
    77  	}
    78  
    79  	responseL := netListenerInRange(portRangeMin, portRangeMax)
    80  	args.ResponseAddress = responseL.Addr().String()
    81  
    82  	go func() {
    83  		defer responseL.Close()
    84  
    85  		conn, err := responseL.Accept()
    86  		if err != nil {
    87  			cmd.SetExited(123)
    88  			return
    89  		}
    90  
    91  		defer conn.Close()
    92  
    93  		decoder := gob.NewDecoder(conn)
    94  
    95  		var finished CommandFinished
    96  		if err := decoder.Decode(&finished); err != nil {
    97  			cmd.SetExited(123)
    98  			return
    99  		}
   100  
   101  		cmd.SetExited(finished.ExitStatus)
   102  	}()
   103  
   104  	err = c.client.Call("Communicator.Start", &args, new(interface{}))
   105  	return
   106  }
   107  
   108  func (c *communicator) Upload(path string, r io.Reader) (err error) {
   109  	// We need to create a server that can proxy the reader data
   110  	// over because we can't simply gob encode an io.Reader
   111  	readerL := netListenerInRange(portRangeMin, portRangeMax)
   112  	if readerL == nil {
   113  		err = errors.New("couldn't allocate listener for upload reader")
   114  		return
   115  	}
   116  
   117  	// Make sure at the end of this call, we close the listener
   118  	defer readerL.Close()
   119  
   120  	// Pipe the reader through to the connection
   121  	go serveSingleCopy("uploadReader", readerL, nil, r)
   122  
   123  	args := CommunicatorUploadArgs{
   124  		path,
   125  		readerL.Addr().String(),
   126  	}
   127  
   128  	err = c.client.Call("Communicator.Upload", &args, new(interface{}))
   129  	return
   130  }
   131  
   132  func (c *communicator) UploadDir(dst string, src string, exclude []string) error {
   133  	args := &CommunicatorUploadDirArgs{
   134  		Dst:     dst,
   135  		Src:     src,
   136  		Exclude: exclude,
   137  	}
   138  
   139  	var reply error
   140  	err := c.client.Call("Communicator.UploadDir", args, &reply)
   141  	if err == nil {
   142  		err = reply
   143  	}
   144  
   145  	return err
   146  }
   147  
   148  func (c *communicator) Download(path string, w io.Writer) (err error) {
   149  	// We need to create a server that can proxy that data downloaded
   150  	// into the writer because we can't gob encode a writer directly.
   151  	writerL := netListenerInRange(portRangeMin, portRangeMax)
   152  	if writerL == nil {
   153  		err = errors.New("couldn't allocate listener for download writer")
   154  		return
   155  	}
   156  
   157  	// Make sure we close the listener once we're done because we'll be done
   158  	defer writerL.Close()
   159  
   160  	// Serve a single connection and a single copy
   161  	go serveSingleCopy("downloadWriter", writerL, w, nil)
   162  
   163  	args := CommunicatorDownloadArgs{
   164  		path,
   165  		writerL.Addr().String(),
   166  	}
   167  
   168  	err = c.client.Call("Communicator.Download", &args, new(interface{}))
   169  	return
   170  }
   171  
   172  func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface{}) (err error) {
   173  	// Build the RemoteCmd on this side so that it all pipes over
   174  	// to the remote side.
   175  	var cmd packer.RemoteCmd
   176  	cmd.Command = args.Command
   177  
   178  	toClose := make([]net.Conn, 0)
   179  	if args.StdinAddress != "" {
   180  		stdinC, err := net.Dial("tcp", args.StdinAddress)
   181  		if err != nil {
   182  			return err
   183  		}
   184  
   185  		toClose = append(toClose, stdinC)
   186  		cmd.Stdin = stdinC
   187  	}
   188  
   189  	if args.StdoutAddress != "" {
   190  		stdoutC, err := net.Dial("tcp", args.StdoutAddress)
   191  		if err != nil {
   192  			return err
   193  		}
   194  
   195  		toClose = append(toClose, stdoutC)
   196  		cmd.Stdout = stdoutC
   197  	}
   198  
   199  	if args.StderrAddress != "" {
   200  		stderrC, err := net.Dial("tcp", args.StderrAddress)
   201  		if err != nil {
   202  			return err
   203  		}
   204  
   205  		toClose = append(toClose, stderrC)
   206  		cmd.Stderr = stderrC
   207  	}
   208  
   209  	// Connect to the response address so we can write our result to it
   210  	// when ready.
   211  	responseC, err := net.Dial("tcp", args.ResponseAddress)
   212  	if err != nil {
   213  		return err
   214  	}
   215  
   216  	responseWriter := gob.NewEncoder(responseC)
   217  
   218  	// Start the actual command
   219  	err = c.c.Start(&cmd)
   220  
   221  	// Start a goroutine to spin and wait for the process to actual
   222  	// exit. When it does, report it back to caller...
   223  	go func() {
   224  		defer responseC.Close()
   225  		for _, conn := range toClose {
   226  			defer conn.Close()
   227  		}
   228  
   229  		cmd.Wait()
   230  		responseWriter.Encode(&CommandFinished{cmd.ExitStatus})
   231  	}()
   232  
   233  	return
   234  }
   235  
   236  func (c *CommunicatorServer) Upload(args *CommunicatorUploadArgs, reply *interface{}) (err error) {
   237  	readerC, err := net.Dial("tcp", args.ReaderAddress)
   238  	if err != nil {
   239  		return
   240  	}
   241  
   242  	defer readerC.Close()
   243  
   244  	err = c.c.Upload(args.Path, readerC)
   245  	return
   246  }
   247  
   248  func (c *CommunicatorServer) UploadDir(args *CommunicatorUploadDirArgs, reply *error) error {
   249  	return c.c.UploadDir(args.Dst, args.Src, args.Exclude)
   250  }
   251  
   252  func (c *CommunicatorServer) Download(args *CommunicatorDownloadArgs, reply *interface{}) (err error) {
   253  	writerC, err := net.Dial("tcp", args.WriterAddress)
   254  	if err != nil {
   255  		return
   256  	}
   257  
   258  	defer writerC.Close()
   259  
   260  	err = c.c.Download(args.Path, writerC)
   261  	return
   262  }
   263  
   264  func serveSingleCopy(name string, l net.Listener, dst io.Writer, src io.Reader) {
   265  	defer l.Close()
   266  
   267  	conn, err := l.Accept()
   268  	if err != nil {
   269  		log.Printf("'%s' accept error: %s", name, err)
   270  		return
   271  	}
   272  
   273  	// Be sure to close the connection after we're done copying so
   274  	// that an EOF will successfully be sent to the remote side
   275  	defer conn.Close()
   276  
   277  	// The connection is the destination/source that is nil
   278  	if dst == nil {
   279  		dst = conn
   280  	} else {
   281  		src = conn
   282  	}
   283  
   284  	written, err := io.Copy(dst, src)
   285  	log.Printf("%d bytes written for '%s'", written, name)
   286  	if err != nil {
   287  		log.Printf("'%s' copy error: %s", name, err)
   288  	}
   289  }