github.com/amanya/packer@v0.12.1-0.20161117214323-902ac5ab2eb6/packer/rpc/communicator.go (about)

     1  package rpc
     2  
     3  import (
     4  	"encoding/gob"
     5  	"io"
     6  	"log"
     7  	"net/rpc"
     8  	"os"
     9  
    10  	"github.com/mitchellh/packer/packer"
    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  	mux    *muxBroker
    18  }
    19  
    20  // CommunicatorServer wraps a packer.Communicator implementation and makes
    21  // it exportable as part of a Golang RPC server.
    22  type CommunicatorServer struct {
    23  	c   packer.Communicator
    24  	mux *muxBroker
    25  }
    26  
    27  type CommandFinished struct {
    28  	ExitStatus int
    29  }
    30  
    31  type CommunicatorStartArgs struct {
    32  	Command          string
    33  	StdinStreamId    uint32
    34  	StdoutStreamId   uint32
    35  	StderrStreamId   uint32
    36  	ResponseStreamId uint32
    37  }
    38  
    39  type CommunicatorDownloadArgs struct {
    40  	Path           string
    41  	WriterStreamId uint32
    42  }
    43  
    44  type CommunicatorUploadArgs struct {
    45  	Path           string
    46  	ReaderStreamId uint32
    47  	FileInfo       *fileInfo
    48  }
    49  
    50  type CommunicatorUploadDirArgs struct {
    51  	Dst     string
    52  	Src     string
    53  	Exclude []string
    54  }
    55  
    56  type CommunicatorDownloadDirArgs struct {
    57  	Dst     string
    58  	Src     string
    59  	Exclude []string
    60  }
    61  
    62  func Communicator(client *rpc.Client) *communicator {
    63  	return &communicator{client: client}
    64  }
    65  
    66  func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
    67  	var args CommunicatorStartArgs
    68  	args.Command = cmd.Command
    69  
    70  	if cmd.Stdin != nil {
    71  		args.StdinStreamId = c.mux.NextId()
    72  		go serveSingleCopy("stdin", c.mux, args.StdinStreamId, nil, cmd.Stdin)
    73  	}
    74  
    75  	if cmd.Stdout != nil {
    76  		args.StdoutStreamId = c.mux.NextId()
    77  		go serveSingleCopy("stdout", c.mux, args.StdoutStreamId, cmd.Stdout, nil)
    78  	}
    79  
    80  	if cmd.Stderr != nil {
    81  		args.StderrStreamId = c.mux.NextId()
    82  		go serveSingleCopy("stderr", c.mux, args.StderrStreamId, cmd.Stderr, nil)
    83  	}
    84  
    85  	responseStreamId := c.mux.NextId()
    86  	args.ResponseStreamId = responseStreamId
    87  
    88  	go func() {
    89  		conn, err := c.mux.Accept(responseStreamId)
    90  		if err != nil {
    91  			log.Printf("[ERR] Error accepting response stream %d: %s",
    92  				responseStreamId, err)
    93  			cmd.SetExited(123)
    94  			return
    95  		}
    96  		defer conn.Close()
    97  
    98  		var finished CommandFinished
    99  		decoder := gob.NewDecoder(conn)
   100  		if err := decoder.Decode(&finished); err != nil {
   101  			log.Printf("[ERR] Error decoding response stream %d: %s",
   102  				responseStreamId, err)
   103  			cmd.SetExited(123)
   104  			return
   105  		}
   106  
   107  		log.Printf("[INFO] RPC client: Communicator ended with: %d", finished.ExitStatus)
   108  		cmd.SetExited(finished.ExitStatus)
   109  	}()
   110  
   111  	err = c.client.Call("Communicator.Start", &args, new(interface{}))
   112  	return
   113  }
   114  
   115  func (c *communicator) Upload(path string, r io.Reader, fi *os.FileInfo) (err error) {
   116  	// Pipe the reader through to the connection
   117  	streamId := c.mux.NextId()
   118  	go serveSingleCopy("uploadData", c.mux, streamId, nil, r)
   119  
   120  	args := CommunicatorUploadArgs{
   121  		Path:           path,
   122  		ReaderStreamId: streamId,
   123  	}
   124  
   125  	if fi != nil {
   126  		args.FileInfo = NewFileInfo(*fi)
   127  	}
   128  
   129  	err = c.client.Call("Communicator.Upload", &args, new(interface{}))
   130  	return
   131  }
   132  
   133  func (c *communicator) UploadDir(dst string, src string, exclude []string) error {
   134  	args := &CommunicatorUploadDirArgs{
   135  		Dst:     dst,
   136  		Src:     src,
   137  		Exclude: exclude,
   138  	}
   139  
   140  	var reply error
   141  	err := c.client.Call("Communicator.UploadDir", args, &reply)
   142  	if err == nil {
   143  		err = reply
   144  	}
   145  
   146  	return err
   147  }
   148  
   149  func (c *communicator) DownloadDir(src string, dst string, exclude []string) error {
   150  	args := &CommunicatorDownloadDirArgs{
   151  		Dst:     dst,
   152  		Src:     src,
   153  		Exclude: exclude,
   154  	}
   155  
   156  	var reply error
   157  	err := c.client.Call("Communicator.DownloadDir", args, &reply)
   158  	if err == nil {
   159  		err = reply
   160  	}
   161  
   162  	return err
   163  }
   164  
   165  func (c *communicator) Download(path string, w io.Writer) (err error) {
   166  	// Serve a single connection and a single copy
   167  	streamId := c.mux.NextId()
   168  
   169  	waitServer := make(chan struct{})
   170  	go func() {
   171  		serveSingleCopy("downloadWriter", c.mux, streamId, w, nil)
   172  		close(waitServer)
   173  	}()
   174  
   175  	args := CommunicatorDownloadArgs{
   176  		Path:           path,
   177  		WriterStreamId: streamId,
   178  	}
   179  
   180  	// Start sending data to the RPC server
   181  	err = c.client.Call("Communicator.Download", &args, new(interface{}))
   182  
   183  	// Wait for the RPC server to finish receiving the data before we return
   184  	<-waitServer
   185  
   186  	return
   187  }
   188  
   189  func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface{}) error {
   190  	// Build the RemoteCmd on this side so that it all pipes over
   191  	// to the remote side.
   192  	var cmd packer.RemoteCmd
   193  	cmd.Command = args.Command
   194  
   195  	// Create a channel to signal we're done so that we can close
   196  	// our stdin/stdout/stderr streams
   197  	toClose := make([]io.Closer, 0)
   198  	doneCh := make(chan struct{})
   199  	go func() {
   200  		<-doneCh
   201  		for _, conn := range toClose {
   202  			defer conn.Close()
   203  		}
   204  	}()
   205  
   206  	if args.StdinStreamId > 0 {
   207  		conn, err := c.mux.Dial(args.StdinStreamId)
   208  		if err != nil {
   209  			close(doneCh)
   210  			return NewBasicError(err)
   211  		}
   212  
   213  		toClose = append(toClose, conn)
   214  		cmd.Stdin = conn
   215  	}
   216  
   217  	if args.StdoutStreamId > 0 {
   218  		conn, err := c.mux.Dial(args.StdoutStreamId)
   219  		if err != nil {
   220  			close(doneCh)
   221  			return NewBasicError(err)
   222  		}
   223  
   224  		toClose = append(toClose, conn)
   225  		cmd.Stdout = conn
   226  	}
   227  
   228  	if args.StderrStreamId > 0 {
   229  		conn, err := c.mux.Dial(args.StderrStreamId)
   230  		if err != nil {
   231  			close(doneCh)
   232  			return NewBasicError(err)
   233  		}
   234  
   235  		toClose = append(toClose, conn)
   236  		cmd.Stderr = conn
   237  	}
   238  
   239  	// Connect to the response address so we can write our result to it
   240  	// when ready.
   241  	responseC, err := c.mux.Dial(args.ResponseStreamId)
   242  	if err != nil {
   243  		close(doneCh)
   244  		return NewBasicError(err)
   245  	}
   246  	responseWriter := gob.NewEncoder(responseC)
   247  
   248  	// Start the actual command
   249  	err = c.c.Start(&cmd)
   250  	if err != nil {
   251  		close(doneCh)
   252  		return NewBasicError(err)
   253  	}
   254  
   255  	// Start a goroutine to spin and wait for the process to actual
   256  	// exit. When it does, report it back to caller...
   257  	go func() {
   258  		defer close(doneCh)
   259  		defer responseC.Close()
   260  		cmd.Wait()
   261  		log.Printf("[INFO] RPC endpoint: Communicator ended with: %d", cmd.ExitStatus)
   262  		responseWriter.Encode(&CommandFinished{cmd.ExitStatus})
   263  	}()
   264  
   265  	return nil
   266  }
   267  
   268  func (c *CommunicatorServer) Upload(args *CommunicatorUploadArgs, reply *interface{}) (err error) {
   269  	readerC, err := c.mux.Dial(args.ReaderStreamId)
   270  	if err != nil {
   271  		return
   272  	}
   273  	defer readerC.Close()
   274  
   275  	var fi *os.FileInfo
   276  	if args.FileInfo != nil {
   277  		fi = new(os.FileInfo)
   278  		*fi = *args.FileInfo
   279  	}
   280  	err = c.c.Upload(args.Path, readerC, fi)
   281  	return
   282  }
   283  
   284  func (c *CommunicatorServer) UploadDir(args *CommunicatorUploadDirArgs, reply *error) error {
   285  	return c.c.UploadDir(args.Dst, args.Src, args.Exclude)
   286  }
   287  
   288  func (c *CommunicatorServer) DownloadDir(args *CommunicatorUploadDirArgs, reply *error) error {
   289  	return c.c.DownloadDir(args.Src, args.Dst, args.Exclude)
   290  }
   291  
   292  func (c *CommunicatorServer) Download(args *CommunicatorDownloadArgs, reply *interface{}) (err error) {
   293  	writerC, err := c.mux.Dial(args.WriterStreamId)
   294  	if err != nil {
   295  		return
   296  	}
   297  	defer writerC.Close()
   298  
   299  	err = c.c.Download(args.Path, writerC)
   300  	return
   301  }
   302  
   303  func serveSingleCopy(name string, mux *muxBroker, id uint32, dst io.Writer, src io.Reader) {
   304  	conn, err := mux.Accept(id)
   305  	if err != nil {
   306  		log.Printf("[ERR] '%s' accept error: %s", name, err)
   307  		return
   308  	}
   309  
   310  	// Be sure to close the connection after we're done copying so
   311  	// that an EOF will successfully be sent to the remote side
   312  	defer conn.Close()
   313  
   314  	// The connection is the destination/source that is nil
   315  	if dst == nil {
   316  		dst = conn
   317  	} else {
   318  		src = conn
   319  	}
   320  
   321  	written, err := io.Copy(dst, src)
   322  	log.Printf("[INFO] %d bytes written for '%s'", written, name)
   323  	if err != nil {
   324  		log.Printf("[ERR] '%s' copy error: %s", name, err)
   325  	}
   326  }