github.com/jonasi/terraform@v0.6.10-0.20160125170522-e865c342cc1f/plugin/server.go (about)

     1  package plugin
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"net"
     9  	"os"
    10  	"os/signal"
    11  	"runtime"
    12  	"strconv"
    13  	"sync/atomic"
    14  
    15  	tfrpc "github.com/hashicorp/terraform/rpc"
    16  )
    17  
    18  // The APIVersion is outputted along with the RPC address. The plugin
    19  // client validates this API version and will show an error if it doesn't
    20  // know how to speak it.
    21  const APIVersion = "2"
    22  
    23  // The "magic cookie" is used to verify that the user intended to
    24  // actually run this binary. If this cookie isn't present as an
    25  // environmental variable, then we bail out early with an error.
    26  const MagicCookieKey = "TF_PLUGIN_MAGIC_COOKIE"
    27  const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d6991ca9872b2"
    28  
    29  // ServeOpts configures what sorts of plugins are served.
    30  type ServeOpts struct {
    31  	ProviderFunc    tfrpc.ProviderFunc
    32  	ProvisionerFunc tfrpc.ProvisionerFunc
    33  }
    34  
    35  // Serve serves the plugins given by ServeOpts.
    36  //
    37  // Serve doesn't return until the plugin is done being executed. Any
    38  // errors will be outputted to the log.
    39  func Serve(opts *ServeOpts) {
    40  	// First check the cookie
    41  	if os.Getenv(MagicCookieKey) != MagicCookieValue {
    42  		fmt.Fprintf(os.Stderr,
    43  			"This binary is a Terraform plugin. These are not meant to be\n"+
    44  				"executed directly. Please execute `terraform`, which will load\n"+
    45  				"any plugins automatically.\n")
    46  		os.Exit(1)
    47  	}
    48  
    49  	// Register a listener so we can accept a connection
    50  	listener, err := serverListener()
    51  	if err != nil {
    52  		log.Printf("[ERR] plugin init: %s", err)
    53  		return
    54  	}
    55  	defer listener.Close()
    56  
    57  	// Create the RPC server to dispense
    58  	server := &tfrpc.Server{
    59  		ProviderFunc:    opts.ProviderFunc,
    60  		ProvisionerFunc: opts.ProvisionerFunc,
    61  	}
    62  
    63  	// Output the address and service name to stdout so that Terraform
    64  	// core can bring it up.
    65  	log.Printf("Plugin address: %s %s\n",
    66  		listener.Addr().Network(), listener.Addr().String())
    67  	fmt.Printf("%s|%s|%s\n",
    68  		APIVersion,
    69  		listener.Addr().Network(),
    70  		listener.Addr().String())
    71  	os.Stdout.Sync()
    72  
    73  	// Eat the interrupts
    74  	ch := make(chan os.Signal, 1)
    75  	signal.Notify(ch, os.Interrupt)
    76  	go func() {
    77  		var count int32 = 0
    78  		for {
    79  			<-ch
    80  			newCount := atomic.AddInt32(&count, 1)
    81  			log.Printf(
    82  				"Received interrupt signal (count: %d). Ignoring.",
    83  				newCount)
    84  		}
    85  	}()
    86  
    87  	// Serve
    88  	server.Accept(listener)
    89  }
    90  
    91  func serverListener() (net.Listener, error) {
    92  	if runtime.GOOS == "windows" {
    93  		return serverListener_tcp()
    94  	}
    95  
    96  	return serverListener_unix()
    97  }
    98  
    99  func serverListener_tcp() (net.Listener, error) {
   100  	minPort, err := strconv.ParseInt(os.Getenv("TF_PLUGIN_MIN_PORT"), 10, 32)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	maxPort, err := strconv.ParseInt(os.Getenv("TF_PLUGIN_MAX_PORT"), 10, 32)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	for port := minPort; port <= maxPort; port++ {
   111  		address := fmt.Sprintf("127.0.0.1:%d", port)
   112  		listener, err := net.Listen("tcp", address)
   113  		if err == nil {
   114  			return listener, nil
   115  		}
   116  	}
   117  
   118  	return nil, errors.New("Couldn't bind plugin TCP listener")
   119  }
   120  
   121  func serverListener_unix() (net.Listener, error) {
   122  	tf, err := ioutil.TempFile("", "tf-plugin")
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	path := tf.Name()
   127  
   128  	// Close the file and remove it because it has to not exist for
   129  	// the domain socket.
   130  	if err := tf.Close(); err != nil {
   131  		return nil, err
   132  	}
   133  	if err := os.Remove(path); err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	return net.Listen("unix", path)
   138  }