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