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