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  }