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