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 }