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  }