github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/plugin/server.go (about)

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