github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/packer/plugin/server.go (about)

     1  // The plugin package provides the functionality to both expose a Packer
     2  // plugin binary and to connect to an existing Packer plugin binary.
     3  //
     4  // Packer supports plugins in the form of self-contained external static
     5  // Go binaries. These binaries behave in a certain way (enforced by this
     6  // package) and are connected to in a certain way (also enforced by this
     7  // package).
     8  package plugin
     9  
    10  import (
    11  	"errors"
    12  	"fmt"
    13  	packrpc "github.com/mitchellh/packer/packer/rpc"
    14  	"io/ioutil"
    15  	"log"
    16  	"net"
    17  	"os"
    18  	"os/signal"
    19  	"runtime"
    20  	"strconv"
    21  	"sync/atomic"
    22  )
    23  
    24  // This is a count of the number of interrupts the process has received.
    25  // This is updated with sync/atomic whenever a SIGINT is received and can
    26  // be checked by the plugin safely to take action.
    27  var Interrupts int32 = 0
    28  
    29  const MagicCookieKey = "PACKER_PLUGIN_MAGIC_COOKIE"
    30  const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d6991ca9872b2"
    31  
    32  // The APIVersion is outputted along with the RPC address. The plugin
    33  // client validates this API version and will show an error if it doesn't
    34  // know how to speak it.
    35  const APIVersion = "4"
    36  
    37  // Server waits for a connection to this plugin and returns a Packer
    38  // RPC server that you can use to register components and serve them.
    39  func Server() (*packrpc.Server, error) {
    40  	if os.Getenv(MagicCookieKey) != MagicCookieValue {
    41  		return nil, errors.New(
    42  			"Please do not execute plugins directly. Packer will execute these for you.")
    43  	}
    44  
    45  	// If there is no explicit number of Go threads to use, then set it
    46  	if os.Getenv("GOMAXPROCS") == "" {
    47  		runtime.GOMAXPROCS(runtime.NumCPU())
    48  	}
    49  
    50  	minPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MIN_PORT"), 10, 32)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	maxPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MAX_PORT"), 10, 32)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	log.Printf("Plugin minimum port: %d\n", minPort)
    61  	log.Printf("Plugin maximum port: %d\n", maxPort)
    62  
    63  	listener, err := serverListener(minPort, maxPort)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	defer listener.Close()
    68  
    69  	// Output the address to stdout
    70  	log.Printf("Plugin address: %s %s\n",
    71  		listener.Addr().Network(), listener.Addr().String())
    72  	fmt.Printf("%s|%s|%s\n",
    73  		APIVersion,
    74  		listener.Addr().Network(),
    75  		listener.Addr().String())
    76  	os.Stdout.Sync()
    77  
    78  	// Accept a connection
    79  	log.Println("Waiting for connection...")
    80  	conn, err := listener.Accept()
    81  	if err != nil {
    82  		log.Printf("Error accepting connection: %s\n", err.Error())
    83  		return nil, err
    84  	}
    85  
    86  	// Eat the interrupts
    87  	ch := make(chan os.Signal, 1)
    88  	signal.Notify(ch, os.Interrupt)
    89  	go func() {
    90  		var count int32 = 0
    91  		for {
    92  			<-ch
    93  			newCount := atomic.AddInt32(&count, 1)
    94  			log.Printf("Received interrupt signal (count: %d). Ignoring.", newCount)
    95  		}
    96  	}()
    97  
    98  	// Serve a single connection
    99  	log.Println("Serving a plugin connection...")
   100  	return packrpc.NewServer(conn), nil
   101  }
   102  
   103  func serverListener(minPort, maxPort int64) (net.Listener, error) {
   104  	if runtime.GOOS == "windows" {
   105  		return serverListener_tcp(minPort, maxPort)
   106  	}
   107  
   108  	return serverListener_unix()
   109  }
   110  
   111  func serverListener_tcp(minPort, maxPort int64) (net.Listener, error) {
   112  	for port := minPort; port <= maxPort; port++ {
   113  		address := fmt.Sprintf("127.0.0.1:%d", port)
   114  		listener, err := net.Listen("tcp", address)
   115  		if err == nil {
   116  			return listener, nil
   117  		}
   118  	}
   119  
   120  	return nil, errors.New("Couldn't bind plugin TCP listener")
   121  }
   122  
   123  func serverListener_unix() (net.Listener, error) {
   124  	tf, err := ioutil.TempFile("", "packer-plugin")
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	path := tf.Name()
   129  
   130  	// Close the file and remove it because it has to not exist for
   131  	// the domain socket.
   132  	if err := tf.Close(); err != nil {
   133  		return nil, err
   134  	}
   135  	if err := os.Remove(path); err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	return net.Listen("unix", path)
   140  }