github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/packer/rpc/communicator.go (about)

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