github.phpd.cn/hashicorp/packer@v1.3.2/packer/rpc/communicator.go (about)

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