github.com/aspring/packer@v0.8.1-0.20150629211158-9db281ac0f89/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 }, 172 Cache: cache, 173 Ui: ui, 174 } 175 176 //setupSignalHandlers(env) 177 178 cli := &cli.CLI{ 179 Args: args, 180 Commands: Commands, 181 HelpFunc: cli.BasicHelpFunc("packer"), 182 HelpWriter: os.Stdout, 183 Version: Version, 184 } 185 186 exitCode, err := cli.Run() 187 if err != nil { 188 fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err) 189 return 1 190 } 191 192 return exitCode 193 } 194 195 // extractMachineReadable checks the args for the machine readable 196 // flag and returns whether or not it is on. It modifies the args 197 // to remove this flag. 198 func extractMachineReadable(args []string) ([]string, bool) { 199 for i, arg := range args { 200 if arg == "-machine-readable" { 201 // We found it. Slice it out. 202 result := make([]string, len(args)-1) 203 copy(result, args[:i]) 204 copy(result[i:], args[i+1:]) 205 return result, true 206 } 207 } 208 209 return args, false 210 } 211 212 func loadConfig() (*config, error) { 213 var config config 214 config.PluginMinPort = 10000 215 config.PluginMaxPort = 25000 216 if err := config.Discover(); err != nil { 217 return nil, err 218 } 219 220 configFilePath := os.Getenv("PACKER_CONFIG") 221 if configFilePath == "" { 222 var err error 223 configFilePath, err = configFile() 224 225 if err != nil { 226 log.Printf("Error detecting default config file path: %s", err) 227 } 228 } 229 230 if configFilePath == "" { 231 return &config, nil 232 } 233 234 log.Printf("Attempting to open config file: %s", configFilePath) 235 f, err := os.Open(configFilePath) 236 if err != nil { 237 if !os.IsNotExist(err) { 238 return nil, err 239 } 240 241 log.Printf("[WARN] Config file doesn't exist: %s", configFilePath) 242 return &config, nil 243 } 244 defer f.Close() 245 246 if err := decodeConfig(f, &config); err != nil { 247 return nil, err 248 } 249 250 return &config, nil 251 } 252 253 // copyOutput uses output prefixes to determine whether data on stdout 254 // should go to stdout or stderr. This is due to panicwrap using stderr 255 // as the log and error channel. 256 func copyOutput(r io.Reader, doneCh chan<- struct{}) { 257 defer close(doneCh) 258 259 pr, err := prefixedio.NewReader(r) 260 if err != nil { 261 panic(err) 262 } 263 264 stderrR, err := pr.Prefix(ErrorPrefix) 265 if err != nil { 266 panic(err) 267 } 268 stdoutR, err := pr.Prefix(OutputPrefix) 269 if err != nil { 270 panic(err) 271 } 272 defaultR, err := pr.Prefix("") 273 if err != nil { 274 panic(err) 275 } 276 277 var wg sync.WaitGroup 278 wg.Add(3) 279 go func() { 280 defer wg.Done() 281 io.Copy(os.Stderr, stderrR) 282 }() 283 go func() { 284 defer wg.Done() 285 io.Copy(os.Stdout, stdoutR) 286 }() 287 go func() { 288 defer wg.Done() 289 io.Copy(os.Stdout, defaultR) 290 }() 291 292 wg.Wait() 293 }