github.com/kikitux/packer@v0.10.1-0.20160322154024-6237df566f9f/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 // But do not switch to pipe in plugin 116 if os.Getenv(plugin.MagicCookieKey) != plugin.MagicCookieValue { 117 setupStdin() 118 } 119 120 config, err := loadConfig() 121 if err != nil { 122 fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err) 123 return 1 124 } 125 log.Printf("Packer config: %+v", config) 126 127 // Fire off the checkpoint. 128 go runCheckpoint(config) 129 130 cacheDir := os.Getenv("PACKER_CACHE_DIR") 131 if cacheDir == "" { 132 cacheDir = "packer_cache" 133 } 134 135 cacheDir, err = filepath.Abs(cacheDir) 136 if err != nil { 137 fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err) 138 return 1 139 } 140 141 log.Printf("Setting cache directory: %s", cacheDir) 142 cache := &packer.FileCache{CacheDir: cacheDir} 143 144 // Determine if we're in machine-readable mode by mucking around with 145 // the arguments... 146 args, machineReadable := extractMachineReadable(os.Args[1:]) 147 148 defer plugin.CleanupClients() 149 150 // Setup the UI if we're being machine-readable 151 var ui packer.Ui = &packer.BasicUi{ 152 Reader: os.Stdin, 153 Writer: os.Stdout, 154 ErrorWriter: os.Stdout, 155 } 156 if machineReadable { 157 ui = &packer.MachineReadableUi{ 158 Writer: os.Stdout, 159 } 160 161 // Set this so that we don't get colored output in our machine- 162 // readable UI. 163 if err := os.Setenv("PACKER_NO_COLOR", "1"); err != nil { 164 fmt.Fprintf(os.Stderr, "Packer failed to initialize UI: %s\n", err) 165 return 1 166 } 167 } 168 169 // Create the CLI meta 170 CommandMeta = &command.Meta{ 171 CoreConfig: &packer.CoreConfig{ 172 Components: packer.ComponentFinder{ 173 Builder: config.LoadBuilder, 174 Hook: config.LoadHook, 175 PostProcessor: config.LoadPostProcessor, 176 Provisioner: config.LoadProvisioner, 177 }, 178 Version: Version, 179 }, 180 Cache: cache, 181 Ui: ui, 182 } 183 184 //setupSignalHandlers(env) 185 186 cli := &cli.CLI{ 187 Args: args, 188 Commands: Commands, 189 HelpFunc: excludeHelpFunc(Commands, []string{"plugin"}), 190 HelpWriter: os.Stdout, 191 Version: Version, 192 } 193 194 exitCode, err := cli.Run() 195 if err != nil { 196 fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err) 197 return 1 198 } 199 200 return exitCode 201 } 202 203 // excludeHelpFunc filters commands we don't want to show from the list of 204 // commands displayed in packer's help text. 205 func excludeHelpFunc(commands map[string]cli.CommandFactory, exclude []string) cli.HelpFunc { 206 // Make search slice into a map so we can use use the `if found` idiom 207 // instead of a nested loop. 208 var excludes = make(map[string]interface{}, len(exclude)) 209 for _, item := range exclude { 210 excludes[item] = nil 211 } 212 213 // Create filtered list of commands 214 helpCommands := []string{} 215 for command := range commands { 216 if _, found := excludes[command]; !found { 217 helpCommands = append(helpCommands, command) 218 } 219 } 220 221 return cli.FilteredHelpFunc(helpCommands, cli.BasicHelpFunc("packer")) 222 } 223 224 // extractMachineReadable checks the args for the machine readable 225 // flag and returns whether or not it is on. It modifies the args 226 // to remove this flag. 227 func extractMachineReadable(args []string) ([]string, bool) { 228 for i, arg := range args { 229 if arg == "-machine-readable" { 230 // We found it. Slice it out. 231 result := make([]string, len(args)-1) 232 copy(result, args[:i]) 233 copy(result[i:], args[i+1:]) 234 return result, true 235 } 236 } 237 238 return args, false 239 } 240 241 func loadConfig() (*config, error) { 242 var config config 243 config.PluginMinPort = 10000 244 config.PluginMaxPort = 25000 245 if err := config.Discover(); err != nil { 246 return nil, err 247 } 248 249 configFilePath := os.Getenv("PACKER_CONFIG") 250 if configFilePath == "" { 251 var err error 252 configFilePath, err = packer.ConfigFile() 253 254 if err != nil { 255 log.Printf("Error detecting default config file path: %s", err) 256 } 257 } 258 259 if configFilePath == "" { 260 return &config, nil 261 } 262 263 log.Printf("Attempting to open config file: %s", configFilePath) 264 f, err := os.Open(configFilePath) 265 if err != nil { 266 if !os.IsNotExist(err) { 267 return nil, err 268 } 269 270 log.Printf("[WARN] Config file doesn't exist: %s", configFilePath) 271 return &config, nil 272 } 273 defer f.Close() 274 275 if err := decodeConfig(f, &config); err != nil { 276 return nil, err 277 } 278 279 return &config, nil 280 } 281 282 // copyOutput uses output prefixes to determine whether data on stdout 283 // should go to stdout or stderr. This is due to panicwrap using stderr 284 // as the log and error channel. 285 func copyOutput(r io.Reader, doneCh chan<- struct{}) { 286 defer close(doneCh) 287 288 pr, err := prefixedio.NewReader(r) 289 if err != nil { 290 panic(err) 291 } 292 293 stderrR, err := pr.Prefix(ErrorPrefix) 294 if err != nil { 295 panic(err) 296 } 297 stdoutR, err := pr.Prefix(OutputPrefix) 298 if err != nil { 299 panic(err) 300 } 301 defaultR, err := pr.Prefix("") 302 if err != nil { 303 panic(err) 304 } 305 306 var wg sync.WaitGroup 307 wg.Add(3) 308 go func() { 309 defer wg.Done() 310 io.Copy(os.Stderr, stderrR) 311 }() 312 go func() { 313 defer wg.Done() 314 io.Copy(os.Stdout, stdoutR) 315 }() 316 go func() { 317 defer wg.Done() 318 io.Copy(os.Stdout, defaultR) 319 }() 320 321 wg.Wait() 322 } 323 324 func init() { 325 // Seed the random number generator 326 rand.Seed(time.Now().UTC().UnixNano()) 327 }