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