github.com/ttysteale/packer@v0.8.2-0.20150708160520-e5f8ea386ed8/main.go (about) 1 // This is the main package for the `packer` application. 2 package main 3 4 import ( 5 "fmt" 6 "io" 7 "io/ioutil" 8 "log" 9 "os" 10 "path/filepath" 11 "runtime" 12 "sync" 13 14 "github.com/mitchellh/cli" 15 "github.com/mitchellh/packer/command" 16 "github.com/mitchellh/packer/packer" 17 "github.com/mitchellh/packer/packer/plugin" 18 "github.com/mitchellh/panicwrap" 19 "github.com/mitchellh/prefixedio" 20 ) 21 22 func main() { 23 // Call realMain instead of doing the work here so we can use 24 // `defer` statements within the function and have them work properly. 25 // (defers aren't called with os.Exit) 26 os.Exit(realMain()) 27 } 28 29 // realMain is executed from main and returns the exit status to exit with. 30 func realMain() int { 31 var wrapConfig panicwrap.WrapConfig 32 33 if !panicwrap.Wrapped(&wrapConfig) { 34 // Determine where logs should go in general (requested by the user) 35 logWriter, err := logOutput() 36 if err != nil { 37 fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err) 38 return 1 39 } 40 if logWriter == nil { 41 logWriter = ioutil.Discard 42 } 43 44 // We always send logs to a temporary file that we use in case 45 // there is a panic. Otherwise, we delete it. 46 logTempFile, err := ioutil.TempFile("", "packer-log") 47 if err != nil { 48 fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err) 49 return 1 50 } 51 defer os.Remove(logTempFile.Name()) 52 defer logTempFile.Close() 53 54 // Tell the logger to log to this file 55 os.Setenv(EnvLog, "") 56 os.Setenv(EnvLogFile, "") 57 58 // Setup the prefixed readers that send data properly to 59 // stdout/stderr. 60 doneCh := make(chan struct{}) 61 outR, outW := io.Pipe() 62 go copyOutput(outR, doneCh) 63 64 // Create the configuration for panicwrap and wrap our executable 65 wrapConfig.Handler = panicHandler(logTempFile) 66 wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter) 67 wrapConfig.Stdout = outW 68 exitStatus, err := panicwrap.Wrap(&wrapConfig) 69 if err != nil { 70 fmt.Fprintf(os.Stderr, "Couldn't start Packer: %s", err) 71 return 1 72 } 73 74 // If >= 0, we're the parent, so just exit 75 if exitStatus >= 0 { 76 // Close the stdout writer so that our copy process can finish 77 outW.Close() 78 79 // Wait for the output copying to finish 80 <-doneCh 81 82 return exitStatus 83 } 84 85 // We're the child, so just close the tempfile we made in order to 86 // save file handles since the tempfile is only used by the parent. 87 logTempFile.Close() 88 } 89 90 // Call the real main 91 return wrappedMain() 92 } 93 94 // wrappedMain is called only when we're wrapped by panicwrap and 95 // returns the exit status to exit with. 96 func wrappedMain() int { 97 // If there is no explicit number of Go threads to use, then set it 98 if os.Getenv("GOMAXPROCS") == "" { 99 runtime.GOMAXPROCS(runtime.NumCPU()) 100 } 101 102 log.SetOutput(os.Stderr) 103 104 log.Printf( 105 "[INFO] Packer version: %s %s %s", 106 Version, VersionPrerelease, GitCommit) 107 log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH) 108 log.Printf("Built with Go Version: %s", runtime.Version()) 109 110 // Prepare stdin for plugin usage by switching it to a pipe 111 setupStdin() 112 113 config, err := loadConfig() 114 if err != nil { 115 fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err) 116 return 1 117 } 118 log.Printf("Packer config: %+v", config) 119 120 // Fire off the checkpoint. 121 go runCheckpoint(config) 122 123 cacheDir := os.Getenv("PACKER_CACHE_DIR") 124 if cacheDir == "" { 125 cacheDir = "packer_cache" 126 } 127 128 cacheDir, err = filepath.Abs(cacheDir) 129 if err != nil { 130 fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err) 131 return 1 132 } 133 134 log.Printf("Setting cache directory: %s", cacheDir) 135 cache := &packer.FileCache{CacheDir: cacheDir} 136 137 // Determine if we're in machine-readable mode by mucking around with 138 // the arguments... 139 args, machineReadable := extractMachineReadable(os.Args[1:]) 140 141 defer plugin.CleanupClients() 142 143 // Setup the UI if we're being machine-readable 144 var ui packer.Ui = &packer.BasicUi{ 145 Reader: os.Stdin, 146 Writer: os.Stdout, 147 ErrorWriter: os.Stdout, 148 } 149 if machineReadable { 150 ui = &packer.MachineReadableUi{ 151 Writer: os.Stdout, 152 } 153 154 // Set this so that we don't get colored output in our machine- 155 // readable UI. 156 if err := os.Setenv("PACKER_NO_COLOR", "1"); err != nil { 157 fmt.Fprintf(os.Stderr, "Packer failed to initialize UI: %s\n", err) 158 return 1 159 } 160 } 161 162 // Create the CLI meta 163 CommandMeta = &command.Meta{ 164 CoreConfig: &packer.CoreConfig{ 165 Components: packer.ComponentFinder{ 166 Builder: config.LoadBuilder, 167 Hook: config.LoadHook, 168 PostProcessor: config.LoadPostProcessor, 169 Provisioner: config.LoadProvisioner, 170 }, 171 Version: Version, 172 }, 173 Cache: cache, 174 Ui: ui, 175 } 176 177 //setupSignalHandlers(env) 178 179 cli := &cli.CLI{ 180 Args: args, 181 Commands: Commands, 182 HelpFunc: cli.BasicHelpFunc("packer"), 183 HelpWriter: os.Stdout, 184 Version: Version, 185 } 186 187 exitCode, err := cli.Run() 188 if err != nil { 189 fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err) 190 return 1 191 } 192 193 return exitCode 194 } 195 196 // extractMachineReadable checks the args for the machine readable 197 // flag and returns whether or not it is on. It modifies the args 198 // to remove this flag. 199 func extractMachineReadable(args []string) ([]string, bool) { 200 for i, arg := range args { 201 if arg == "-machine-readable" { 202 // We found it. Slice it out. 203 result := make([]string, len(args)-1) 204 copy(result, args[:i]) 205 copy(result[i:], args[i+1:]) 206 return result, true 207 } 208 } 209 210 return args, false 211 } 212 213 func loadConfig() (*config, error) { 214 var config config 215 config.PluginMinPort = 10000 216 config.PluginMaxPort = 25000 217 if err := config.Discover(); err != nil { 218 return nil, err 219 } 220 221 configFilePath := os.Getenv("PACKER_CONFIG") 222 if configFilePath == "" { 223 var err error 224 configFilePath, err = configFile() 225 226 if err != nil { 227 log.Printf("Error detecting default config file path: %s", err) 228 } 229 } 230 231 if configFilePath == "" { 232 return &config, nil 233 } 234 235 log.Printf("Attempting to open config file: %s", configFilePath) 236 f, err := os.Open(configFilePath) 237 if err != nil { 238 if !os.IsNotExist(err) { 239 return nil, err 240 } 241 242 log.Printf("[WARN] Config file doesn't exist: %s", configFilePath) 243 return &config, nil 244 } 245 defer f.Close() 246 247 if err := decodeConfig(f, &config); err != nil { 248 return nil, err 249 } 250 251 return &config, nil 252 } 253 254 // copyOutput uses output prefixes to determine whether data on stdout 255 // should go to stdout or stderr. This is due to panicwrap using stderr 256 // as the log and error channel. 257 func copyOutput(r io.Reader, doneCh chan<- struct{}) { 258 defer close(doneCh) 259 260 pr, err := prefixedio.NewReader(r) 261 if err != nil { 262 panic(err) 263 } 264 265 stderrR, err := pr.Prefix(ErrorPrefix) 266 if err != nil { 267 panic(err) 268 } 269 stdoutR, err := pr.Prefix(OutputPrefix) 270 if err != nil { 271 panic(err) 272 } 273 defaultR, err := pr.Prefix("") 274 if err != nil { 275 panic(err) 276 } 277 278 var wg sync.WaitGroup 279 wg.Add(3) 280 go func() { 281 defer wg.Done() 282 io.Copy(os.Stderr, stderrR) 283 }() 284 go func() { 285 defer wg.Done() 286 io.Copy(os.Stdout, stdoutR) 287 }() 288 go func() { 289 defer wg.Done() 290 io.Copy(os.Stdout, defaultR) 291 }() 292 293 wg.Wait() 294 }