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 }