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