github.com/victortrac/packer@v0.7.6-0.20160602180447-63c7fdb6e41f/main.go (about) 1 // This is the main package for the `packer` application. 2 3 //go:generate go run ./scripts/generate-plugins.go 4 package main 5 6 import ( 7 "fmt" 8 "io" 9 "io/ioutil" 10 "log" 11 "math/rand" 12 "os" 13 "path/filepath" 14 "runtime" 15 "sync" 16 "time" 17 18 "github.com/mitchellh/cli" 19 "github.com/mitchellh/packer/command" 20 "github.com/mitchellh/packer/packer" 21 "github.com/mitchellh/packer/packer/plugin" 22 "github.com/mitchellh/packer/version" 23 "github.com/mitchellh/panicwrap" 24 "github.com/mitchellh/prefixedio" 25 ) 26 27 func main() { 28 // Call realMain instead of doing the work here so we can use 29 // `defer` statements within the function and have them work properly. 30 // (defers aren't called with os.Exit) 31 os.Exit(realMain()) 32 } 33 34 // realMain is executed from main and returns the exit status to exit with. 35 func realMain() int { 36 var wrapConfig panicwrap.WrapConfig 37 38 if !panicwrap.Wrapped(&wrapConfig) { 39 // Determine where logs should go in general (requested by the user) 40 logWriter, err := logOutput() 41 if err != nil { 42 fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err) 43 return 1 44 } 45 if logWriter == nil { 46 logWriter = ioutil.Discard 47 } 48 49 // We always send logs to a temporary file that we use in case 50 // there is a panic. Otherwise, we delete it. 51 logTempFile, err := ioutil.TempFile("", "packer-log") 52 if err != nil { 53 fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err) 54 return 1 55 } 56 defer os.Remove(logTempFile.Name()) 57 defer logTempFile.Close() 58 59 // Tell the logger to log to this file 60 os.Setenv(EnvLog, "") 61 os.Setenv(EnvLogFile, "") 62 63 // Setup the prefixed readers that send data properly to 64 // stdout/stderr. 65 doneCh := make(chan struct{}) 66 outR, outW := io.Pipe() 67 go copyOutput(outR, doneCh) 68 69 // Create the configuration for panicwrap and wrap our executable 70 wrapConfig.Handler = panicHandler(logTempFile) 71 wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter) 72 wrapConfig.Stdout = outW 73 exitStatus, err := panicwrap.Wrap(&wrapConfig) 74 if err != nil { 75 fmt.Fprintf(os.Stderr, "Couldn't start Packer: %s", err) 76 return 1 77 } 78 79 // If >= 0, we're the parent, so just exit 80 if exitStatus >= 0 { 81 // Close the stdout writer so that our copy process can finish 82 outW.Close() 83 84 // Wait for the output copying to finish 85 <-doneCh 86 87 return exitStatus 88 } 89 90 // We're the child, so just close the tempfile we made in order to 91 // save file handles since the tempfile is only used by the parent. 92 logTempFile.Close() 93 } 94 95 // Call the real main 96 return wrappedMain() 97 } 98 99 // wrappedMain is called only when we're wrapped by panicwrap and 100 // returns the exit status to exit with. 101 func wrappedMain() int { 102 // If there is no explicit number of Go threads to use, then set it 103 if os.Getenv("GOMAXPROCS") == "" { 104 runtime.GOMAXPROCS(runtime.NumCPU()) 105 } 106 107 log.SetOutput(os.Stderr) 108 109 log.Printf("[INFO] Packer version: %s", version.FormattedVersion()) 110 log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH) 111 log.Printf("Built with Go Version: %s", runtime.Version()) 112 113 // Prepare stdin for plugin usage by switching it to a pipe 114 // But do not switch to pipe in plugin 115 if os.Getenv(plugin.MagicCookieKey) != plugin.MagicCookieValue { 116 setupStdin() 117 } 118 119 config, err := loadConfig() 120 if err != nil { 121 fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err) 122 return 1 123 } 124 log.Printf("Packer config: %+v", config) 125 126 // Fire off the checkpoint. 127 go runCheckpoint(config) 128 129 cacheDir := os.Getenv("PACKER_CACHE_DIR") 130 if cacheDir == "" { 131 cacheDir = "packer_cache" 132 } 133 134 cacheDir, err = filepath.Abs(cacheDir) 135 if err != nil { 136 fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err) 137 return 1 138 } 139 140 log.Printf("Setting cache directory: %s", cacheDir) 141 cache := &packer.FileCache{CacheDir: cacheDir} 142 143 // Determine if we're in machine-readable mode by mucking around with 144 // the arguments... 145 args, machineReadable := extractMachineReadable(os.Args[1:]) 146 147 defer plugin.CleanupClients() 148 149 // Setup the UI if we're being machine-readable 150 var ui packer.Ui = &packer.BasicUi{ 151 Reader: os.Stdin, 152 Writer: os.Stdout, 153 ErrorWriter: os.Stdout, 154 } 155 if machineReadable { 156 ui = &packer.MachineReadableUi{ 157 Writer: os.Stdout, 158 } 159 160 // Set this so that we don't get colored output in our machine- 161 // readable UI. 162 if err := os.Setenv("PACKER_NO_COLOR", "1"); err != nil { 163 fmt.Fprintf(os.Stderr, "Packer failed to initialize UI: %s\n", err) 164 return 1 165 } 166 } 167 168 // Create the CLI meta 169 CommandMeta = &command.Meta{ 170 CoreConfig: &packer.CoreConfig{ 171 Components: packer.ComponentFinder{ 172 Builder: config.LoadBuilder, 173 Hook: config.LoadHook, 174 PostProcessor: config.LoadPostProcessor, 175 Provisioner: config.LoadProvisioner, 176 }, 177 Version: version.Version, 178 }, 179 Cache: cache, 180 Ui: ui, 181 } 182 183 //setupSignalHandlers(env) 184 185 cli := &cli.CLI{ 186 Args: args, 187 Commands: Commands, 188 HelpFunc: excludeHelpFunc(Commands, []string{"plugin"}), 189 HelpWriter: os.Stdout, 190 Version: version.Version, 191 } 192 193 exitCode, err := cli.Run() 194 if err != nil { 195 fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err) 196 return 1 197 } 198 199 return exitCode 200 } 201 202 // excludeHelpFunc filters commands we don't want to show from the list of 203 // commands displayed in packer's help text. 204 func excludeHelpFunc(commands map[string]cli.CommandFactory, exclude []string) cli.HelpFunc { 205 // Make search slice into a map so we can use use the `if found` idiom 206 // instead of a nested loop. 207 var excludes = make(map[string]interface{}, len(exclude)) 208 for _, item := range exclude { 209 excludes[item] = nil 210 } 211 212 // Create filtered list of commands 213 helpCommands := []string{} 214 for command := range commands { 215 if _, found := excludes[command]; !found { 216 helpCommands = append(helpCommands, command) 217 } 218 } 219 220 return cli.FilteredHelpFunc(helpCommands, cli.BasicHelpFunc("packer")) 221 } 222 223 // extractMachineReadable checks the args for the machine readable 224 // flag and returns whether or not it is on. It modifies the args 225 // to remove this flag. 226 func extractMachineReadable(args []string) ([]string, bool) { 227 for i, arg := range args { 228 if arg == "-machine-readable" { 229 // We found it. Slice it out. 230 result := make([]string, len(args)-1) 231 copy(result, args[:i]) 232 copy(result[i:], args[i+1:]) 233 return result, true 234 } 235 } 236 237 return args, false 238 } 239 240 func loadConfig() (*config, error) { 241 var config config 242 config.PluginMinPort = 10000 243 config.PluginMaxPort = 25000 244 if err := config.Discover(); err != nil { 245 return nil, err 246 } 247 248 configFilePath := os.Getenv("PACKER_CONFIG") 249 if configFilePath == "" { 250 var err error 251 configFilePath, err = packer.ConfigFile() 252 253 if err != nil { 254 log.Printf("Error detecting default config file path: %s", err) 255 } 256 } 257 258 if configFilePath == "" { 259 return &config, nil 260 } 261 262 log.Printf("Attempting to open config file: %s", configFilePath) 263 f, err := os.Open(configFilePath) 264 if err != nil { 265 if !os.IsNotExist(err) { 266 return nil, err 267 } 268 269 log.Printf("[WARN] Config file doesn't exist: %s", configFilePath) 270 return &config, nil 271 } 272 defer f.Close() 273 274 if err := decodeConfig(f, &config); err != nil { 275 return nil, err 276 } 277 278 return &config, nil 279 } 280 281 // copyOutput uses output prefixes to determine whether data on stdout 282 // should go to stdout or stderr. This is due to panicwrap using stderr 283 // as the log and error channel. 284 func copyOutput(r io.Reader, doneCh chan<- struct{}) { 285 defer close(doneCh) 286 287 pr, err := prefixedio.NewReader(r) 288 if err != nil { 289 panic(err) 290 } 291 292 stderrR, err := pr.Prefix(ErrorPrefix) 293 if err != nil { 294 panic(err) 295 } 296 stdoutR, err := pr.Prefix(OutputPrefix) 297 if err != nil { 298 panic(err) 299 } 300 defaultR, err := pr.Prefix("") 301 if err != nil { 302 panic(err) 303 } 304 305 var wg sync.WaitGroup 306 wg.Add(3) 307 go func() { 308 defer wg.Done() 309 io.Copy(os.Stderr, stderrR) 310 }() 311 go func() { 312 defer wg.Done() 313 io.Copy(os.Stdout, stdoutR) 314 }() 315 go func() { 316 defer wg.Done() 317 io.Copy(os.Stdout, defaultR) 318 }() 319 320 wg.Wait() 321 } 322 323 func init() { 324 // Seed the random number generator 325 rand.Seed(time.Now().UTC().UnixNano()) 326 }