github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/packer/plugin/server.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 packrpc "github.com/mitchellh/packer/packer/rpc" 14 "io/ioutil" 15 "log" 16 "net" 17 "os" 18 "os/signal" 19 "runtime" 20 "strconv" 21 "sync/atomic" 22 ) 23 24 // This is a count of the number of interrupts the process has received. 25 // This is updated with sync/atomic whenever a SIGINT is received and can 26 // be checked by the plugin safely to take action. 27 var Interrupts int32 = 0 28 29 const MagicCookieKey = "PACKER_PLUGIN_MAGIC_COOKIE" 30 const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d6991ca9872b2" 31 32 // The APIVersion is outputted along with the RPC address. The plugin 33 // client validates this API version and will show an error if it doesn't 34 // know how to speak it. 35 const APIVersion = "4" 36 37 // Server waits for a connection to this plugin and returns a Packer 38 // RPC server that you can use to register components and serve them. 39 func Server() (*packrpc.Server, error) { 40 if os.Getenv(MagicCookieKey) != MagicCookieValue { 41 return nil, errors.New( 42 "Please do not execute plugins directly. Packer will execute these for you.") 43 } 44 45 // If there is no explicit number of Go threads to use, then set it 46 if os.Getenv("GOMAXPROCS") == "" { 47 runtime.GOMAXPROCS(runtime.NumCPU()) 48 } 49 50 minPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MIN_PORT"), 10, 32) 51 if err != nil { 52 return nil, err 53 } 54 55 maxPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MAX_PORT"), 10, 32) 56 if err != nil { 57 return nil, err 58 } 59 60 log.Printf("Plugin minimum port: %d\n", minPort) 61 log.Printf("Plugin maximum port: %d\n", maxPort) 62 63 listener, err := serverListener(minPort, maxPort) 64 if err != nil { 65 return nil, err 66 } 67 defer listener.Close() 68 69 // Output the address to stdout 70 log.Printf("Plugin address: %s %s\n", 71 listener.Addr().Network(), listener.Addr().String()) 72 fmt.Printf("%s|%s|%s\n", 73 APIVersion, 74 listener.Addr().Network(), 75 listener.Addr().String()) 76 os.Stdout.Sync() 77 78 // Accept a connection 79 log.Println("Waiting for connection...") 80 conn, err := listener.Accept() 81 if err != nil { 82 log.Printf("Error accepting connection: %s\n", err.Error()) 83 return nil, err 84 } 85 86 // Eat the interrupts 87 ch := make(chan os.Signal, 1) 88 signal.Notify(ch, os.Interrupt) 89 go func() { 90 var count int32 = 0 91 for { 92 <-ch 93 newCount := atomic.AddInt32(&count, 1) 94 log.Printf("Received interrupt signal (count: %d). Ignoring.", newCount) 95 } 96 }() 97 98 // Serve a single connection 99 log.Println("Serving a plugin connection...") 100 return packrpc.NewServer(conn), nil 101 } 102 103 func serverListener(minPort, maxPort int64) (net.Listener, error) { 104 if runtime.GOOS == "windows" { 105 return serverListener_tcp(minPort, maxPort) 106 } 107 108 return serverListener_unix() 109 } 110 111 func serverListener_tcp(minPort, maxPort int64) (net.Listener, error) { 112 for port := minPort; port <= maxPort; port++ { 113 address := fmt.Sprintf("127.0.0.1:%d", port) 114 listener, err := net.Listen("tcp", address) 115 if err == nil { 116 return listener, nil 117 } 118 } 119 120 return nil, errors.New("Couldn't bind plugin TCP listener") 121 } 122 123 func serverListener_unix() (net.Listener, error) { 124 tf, err := ioutil.TempFile("", "packer-plugin") 125 if err != nil { 126 return nil, err 127 } 128 path := tf.Name() 129 130 // Close the file and remove it because it has to not exist for 131 // the domain socket. 132 if err := tf.Close(); err != nil { 133 return nil, err 134 } 135 if err := os.Remove(path); err != nil { 136 return nil, err 137 } 138 139 return net.Listen("unix", path) 140 }