github.com/supr/packer@v0.3.10-0.20131015195147-7b09e24ac3c1/packer/plugin/plugin.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  	"log"
    16  	"net"
    17  	"net/rpc"
    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 = "1"
    37  
    38  // This serves a single RPC connection on the given RPC server on
    39  // a random port.
    40  func serve(server *rpc.Server) (err error) {
    41  	log.Printf("Plugin build against Packer '%s'", packer.GitCommit)
    42  
    43  	if os.Getenv(MagicCookieKey) != MagicCookieValue {
    44  		return errors.New("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
    55  	}
    56  
    57  	maxPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MAX_PORT"), 10, 32)
    58  	if err != nil {
    59  		return
    60  	}
    61  
    62  	log.Printf("Plugin minimum port: %d\n", minPort)
    63  	log.Printf("Plugin maximum port: %d\n", maxPort)
    64  
    65  	// Set the RPC port range
    66  	packrpc.PortRange(int(minPort), int(maxPort))
    67  
    68  	var address string
    69  	var listener net.Listener
    70  	for port := minPort; port <= maxPort; port++ {
    71  		address = fmt.Sprintf("127.0.0.1:%d", port)
    72  		listener, err = net.Listen("tcp", address)
    73  		if err != nil {
    74  			err = nil
    75  			continue
    76  		}
    77  
    78  		break
    79  	}
    80  
    81  	defer listener.Close()
    82  
    83  	// Output the address to stdout
    84  	log.Printf("Plugin address: %s\n", address)
    85  	fmt.Printf("%s|%s\n", APIVersion, address)
    86  	os.Stdout.Sync()
    87  
    88  	// Accept a connection
    89  	log.Println("Waiting for connection...")
    90  	conn, err := listener.Accept()
    91  	if err != nil {
    92  		log.Printf("Error accepting connection: %s\n", err.Error())
    93  		return
    94  	}
    95  
    96  	// Serve a single connection
    97  	log.Println("Serving a plugin connection...")
    98  	server.ServeConn(conn)
    99  	return
   100  }
   101  
   102  // Registers a signal handler to swallow and count interrupts so that the
   103  // plugin isn't killed. The main host Packer process is responsible
   104  // for killing the plugins when interrupted.
   105  func countInterrupts() {
   106  	ch := make(chan os.Signal, 1)
   107  	signal.Notify(ch, os.Interrupt)
   108  
   109  	go func() {
   110  		for {
   111  			<-ch
   112  			newCount := atomic.AddInt32(&Interrupts, 1)
   113  			log.Printf("Received interrupt signal (count: %d). Ignoring.", newCount)
   114  		}
   115  	}()
   116  }
   117  
   118  // Serves a builder from a plugin.
   119  func ServeBuilder(builder packer.Builder) {
   120  	log.Println("Preparing to serve a builder plugin...")
   121  
   122  	server := rpc.NewServer()
   123  	packrpc.RegisterBuilder(server, builder)
   124  
   125  	countInterrupts()
   126  	if err := serve(server); err != nil {
   127  		log.Printf("ERROR: %s", err)
   128  		os.Exit(1)
   129  	}
   130  }
   131  
   132  // Serves a command from a plugin.
   133  func ServeCommand(command packer.Command) {
   134  	log.Println("Preparing to serve a command plugin...")
   135  
   136  	server := rpc.NewServer()
   137  	packrpc.RegisterCommand(server, command)
   138  
   139  	countInterrupts()
   140  	if err := serve(server); err != nil {
   141  		log.Printf("ERROR: %s", err)
   142  		os.Exit(1)
   143  	}
   144  }
   145  
   146  // Serves a hook from a plugin.
   147  func ServeHook(hook packer.Hook) {
   148  	log.Println("Preparing to serve a hook plugin...")
   149  
   150  	server := rpc.NewServer()
   151  	packrpc.RegisterHook(server, hook)
   152  
   153  	countInterrupts()
   154  	if err := serve(server); err != nil {
   155  		log.Printf("ERROR: %s", err)
   156  		os.Exit(1)
   157  	}
   158  }
   159  
   160  // Serves a post-processor from a plugin.
   161  func ServePostProcessor(p packer.PostProcessor) {
   162  	log.Println("Preparing to serve a post-processor plugin...")
   163  
   164  	server := rpc.NewServer()
   165  	packrpc.RegisterPostProcessor(server, p)
   166  
   167  	countInterrupts()
   168  	if err := serve(server); err != nil {
   169  		log.Printf("ERROR: %s", err)
   170  		os.Exit(1)
   171  	}
   172  }
   173  
   174  // Serves a provisioner from a plugin.
   175  func ServeProvisioner(p packer.Provisioner) {
   176  	log.Println("Preparing to serve a provisioner plugin...")
   177  
   178  	server := rpc.NewServer()
   179  	packrpc.RegisterProvisioner(server, p)
   180  
   181  	countInterrupts()
   182  	if err := serve(server); err != nil {
   183  		log.Printf("ERROR: %s", err)
   184  		os.Exit(1)
   185  	}
   186  }
   187  
   188  // Tests whether or not the plugin was interrupted or not.
   189  func Interrupted() bool {
   190  	return atomic.LoadInt32(&Interrupts) > 0
   191  }