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