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