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