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