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