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