github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/packer.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 13 "github.com/mitchellh/packer/packer" 14 "github.com/mitchellh/packer/packer/plugin" 15 "github.com/mitchellh/panicwrap" 16 ) 17 18 func main() { 19 // Call realMain instead of doing the work here so we can use 20 // `defer` statements within the function and have them work properly. 21 // (defers aren't called with os.Exit) 22 os.Exit(realMain()) 23 } 24 25 // realMain is executed from main and returns the exit status to exit with. 26 func realMain() int { 27 // If there is no explicit number of Go threads to use, then set it 28 if os.Getenv("GOMAXPROCS") == "" { 29 runtime.GOMAXPROCS(runtime.NumCPU()) 30 } 31 32 // Determine where logs should go in general (requested by the user) 33 logWriter, err := logOutput() 34 if err != nil { 35 fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err) 36 return 1 37 } 38 39 // We also always send logs to a temporary file that we use in case 40 // there is a panic. Otherwise, we delete it. 41 logTempFile, err := ioutil.TempFile("", "packer-log") 42 if err != nil { 43 fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err) 44 return 1 45 } 46 defer os.Remove(logTempFile.Name()) 47 defer logTempFile.Close() 48 49 // Reset the log variables to minimize work in the subprocess 50 os.Setenv("PACKER_LOG", "") 51 os.Setenv("PACKER_LOG_FILE", "") 52 53 // Create the configuration for panicwrap and wrap our executable 54 wrapConfig := &panicwrap.WrapConfig{ 55 Handler: panicHandler(logTempFile), 56 Writer: io.MultiWriter(logTempFile, logWriter), 57 } 58 59 exitStatus, err := panicwrap.Wrap(wrapConfig) 60 if err != nil { 61 fmt.Fprintf(os.Stderr, "Couldn't start Packer: %s", err) 62 return 1 63 } 64 65 if exitStatus >= 0 { 66 return exitStatus 67 } 68 69 // We're the child, so just close the tempfile we made in order to 70 // save file handles since the tempfile is only used by the parent. 71 logTempFile.Close() 72 73 return wrappedMain() 74 } 75 76 // wrappedMain is called only when we're wrapped by panicwrap and 77 // returns the exit status to exit with. 78 func wrappedMain() int { 79 log.SetOutput(os.Stderr) 80 81 log.Printf( 82 "Packer Version: %s %s %s", 83 packer.Version, packer.VersionPrerelease, packer.GitCommit) 84 log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH) 85 log.Printf("Built with Go Version: %s", runtime.Version()) 86 87 // Prepare stdin for plugin usage by switching it to a pipe 88 setupStdin() 89 90 config, err := loadConfig() 91 if err != nil { 92 fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err) 93 return 1 94 } 95 log.Printf("Packer config: %+v", config) 96 97 // Fire off the checkpoint. 98 go runCheckpoint(config) 99 100 cacheDir := os.Getenv("PACKER_CACHE_DIR") 101 if cacheDir == "" { 102 cacheDir = "packer_cache" 103 } 104 105 cacheDir, err = filepath.Abs(cacheDir) 106 if err != nil { 107 fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err) 108 return 1 109 } 110 111 log.Printf("Setting cache directory: %s", cacheDir) 112 cache := &packer.FileCache{CacheDir: cacheDir} 113 114 // Determine if we're in machine-readable mode by mucking around with 115 // the arguments... 116 args, machineReadable := extractMachineReadable(os.Args[1:]) 117 118 defer plugin.CleanupClients() 119 120 // Create the environment configuration 121 envConfig := packer.DefaultEnvironmentConfig() 122 envConfig.Cache = cache 123 envConfig.Commands = config.CommandNames() 124 envConfig.Components.Builder = config.LoadBuilder 125 envConfig.Components.Command = config.LoadCommand 126 envConfig.Components.Hook = config.LoadHook 127 envConfig.Components.PostProcessor = config.LoadPostProcessor 128 envConfig.Components.Provisioner = config.LoadProvisioner 129 if machineReadable { 130 envConfig.Ui = &packer.MachineReadableUi{ 131 Writer: os.Stdout, 132 } 133 134 // Set this so that we don't get colored output in our machine- 135 // readable UI. 136 if err := os.Setenv("PACKER_NO_COLOR", "1"); err != nil { 137 fmt.Fprintf(os.Stderr, "Packer failed to initialize UI: %s\n", err) 138 return 1 139 } 140 } 141 142 env, err := packer.NewEnvironment(envConfig) 143 if err != nil { 144 fmt.Fprintf(os.Stderr, "Packer initialization error: \n\n%s\n", err) 145 return 1 146 } 147 148 setupSignalHandlers(env) 149 150 exitCode, err := env.Cli(args) 151 if err != nil { 152 fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) 153 return 1 154 } 155 156 return exitCode 157 } 158 159 // extractMachineReadable checks the args for the machine readable 160 // flag and returns whether or not it is on. It modifies the args 161 // to remove this flag. 162 func extractMachineReadable(args []string) ([]string, bool) { 163 for i, arg := range args { 164 if arg == "-machine-readable" { 165 // We found it. Slice it out. 166 result := make([]string, len(args)-1) 167 copy(result, args[:i]) 168 copy(result[i:], args[i+1:]) 169 return result, true 170 } 171 } 172 173 return args, false 174 } 175 176 func loadConfig() (*config, error) { 177 var config config 178 config.PluginMinPort = 10000 179 config.PluginMaxPort = 25000 180 if err := config.Discover(); err != nil { 181 return nil, err 182 } 183 184 mustExist := true 185 configFilePath := os.Getenv("PACKER_CONFIG") 186 if configFilePath == "" { 187 var err error 188 configFilePath, err = configFile() 189 mustExist = false 190 191 if err != nil { 192 log.Printf("Error detecting default config file path: %s", err) 193 } 194 } 195 196 if configFilePath == "" { 197 return &config, nil 198 } 199 200 log.Printf("Attempting to open config file: %s", configFilePath) 201 f, err := os.Open(configFilePath) 202 if err != nil { 203 if !os.IsNotExist(err) { 204 return nil, err 205 } 206 207 if mustExist { 208 return nil, err 209 } 210 211 log.Println("File doesn't exist, but doesn't need to. Ignoring.") 212 return &config, nil 213 } 214 defer f.Close() 215 216 if err := decodeConfig(f, &config); err != nil { 217 return nil, err 218 } 219 220 return &config, nil 221 } 222 223 // logOutput determines where we should send logs (if anywhere). 224 func logOutput() (logOutput io.Writer, err error) { 225 logOutput = ioutil.Discard 226 if os.Getenv("PACKER_LOG") != "" { 227 logOutput = os.Stderr 228 229 if logPath := os.Getenv("PACKER_LOG_PATH"); logPath != "" { 230 var err error 231 logOutput, err = os.Create(logPath) 232 if err != nil { 233 return nil, err 234 } 235 } 236 } 237 238 return 239 }