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