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