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