github.com/rsyabuta/packer@v1.1.4-0.20180119234903-5ef0c2280f0b/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 log.Printf("'PACKER_CONFIG' set, loading config from environment.") 280 } else { 281 var err error 282 configFilePath, err = packer.ConfigFile() 283 284 if err != nil { 285 log.Printf("Error detecting default config file path: %s", err) 286 } 287 } 288 289 if configFilePath == "" { 290 return &config, nil 291 } 292 293 log.Printf("Attempting to open config file: %s", configFilePath) 294 f, err := os.Open(configFilePath) 295 if err != nil { 296 if !os.IsNotExist(err) { 297 return nil, err 298 } 299 300 log.Printf("[WARN] Config file doesn't exist: %s", configFilePath) 301 return &config, nil 302 } 303 defer f.Close() 304 305 if err := decodeConfig(f, &config); err != nil { 306 return nil, err 307 } 308 309 return &config, nil 310 } 311 312 // copyOutput uses output prefixes to determine whether data on stdout 313 // should go to stdout or stderr. This is due to panicwrap using stderr 314 // as the log and error channel. 315 func copyOutput(r io.Reader, doneCh chan<- struct{}) { 316 defer close(doneCh) 317 318 pr, err := prefixedio.NewReader(r) 319 if err != nil { 320 panic(err) 321 } 322 323 stderrR, err := pr.Prefix(ErrorPrefix) 324 if err != nil { 325 panic(err) 326 } 327 stdoutR, err := pr.Prefix(OutputPrefix) 328 if err != nil { 329 panic(err) 330 } 331 defaultR, err := pr.Prefix("") 332 if err != nil { 333 panic(err) 334 } 335 336 var wg sync.WaitGroup 337 wg.Add(3) 338 go func() { 339 defer wg.Done() 340 io.Copy(os.Stderr, stderrR) 341 }() 342 go func() { 343 defer wg.Done() 344 io.Copy(os.Stdout, stdoutR) 345 }() 346 go func() { 347 defer wg.Done() 348 io.Copy(os.Stdout, defaultR) 349 }() 350 351 wg.Wait() 352 } 353 354 func init() { 355 // Seed the random number generator 356 rand.Seed(time.Now().UTC().UnixNano()) 357 }