github.com/kikitux/packer@v0.10.1-0.20160322154024-6237df566f9f/main.go (about)

     1  // This is the main package for the `packer` application.
     2  
     3  //go:generate go run ./scripts/generate-plugins.go
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"log"
    11  	"math/rand"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/mitchellh/cli"
    19  	"github.com/mitchellh/packer/command"
    20  	"github.com/mitchellh/packer/packer"
    21  	"github.com/mitchellh/packer/packer/plugin"
    22  	"github.com/mitchellh/panicwrap"
    23  	"github.com/mitchellh/prefixedio"
    24  )
    25  
    26  func main() {
    27  	// Call realMain instead of doing the work here so we can use
    28  	// `defer` statements within the function and have them work properly.
    29  	// (defers aren't called with os.Exit)
    30  	os.Exit(realMain())
    31  }
    32  
    33  // realMain is executed from main and returns the exit status to exit with.
    34  func realMain() int {
    35  	var wrapConfig panicwrap.WrapConfig
    36  
    37  	if !panicwrap.Wrapped(&wrapConfig) {
    38  		// Determine where logs should go in general (requested by the user)
    39  		logWriter, err := logOutput()
    40  		if err != nil {
    41  			fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err)
    42  			return 1
    43  		}
    44  		if logWriter == nil {
    45  			logWriter = ioutil.Discard
    46  		}
    47  
    48  		// We always send logs to a temporary file that we use in case
    49  		// there is a panic. Otherwise, we delete it.
    50  		logTempFile, err := ioutil.TempFile("", "packer-log")
    51  		if err != nil {
    52  			fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err)
    53  			return 1
    54  		}
    55  		defer os.Remove(logTempFile.Name())
    56  		defer logTempFile.Close()
    57  
    58  		// Tell the logger to log to this file
    59  		os.Setenv(EnvLog, "")
    60  		os.Setenv(EnvLogFile, "")
    61  
    62  		// Setup the prefixed readers that send data properly to
    63  		// stdout/stderr.
    64  		doneCh := make(chan struct{})
    65  		outR, outW := io.Pipe()
    66  		go copyOutput(outR, doneCh)
    67  
    68  		// Create the configuration for panicwrap and wrap our executable
    69  		wrapConfig.Handler = panicHandler(logTempFile)
    70  		wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter)
    71  		wrapConfig.Stdout = outW
    72  		exitStatus, err := panicwrap.Wrap(&wrapConfig)
    73  		if err != nil {
    74  			fmt.Fprintf(os.Stderr, "Couldn't start Packer: %s", err)
    75  			return 1
    76  		}
    77  
    78  		// If >= 0, we're the parent, so just exit
    79  		if exitStatus >= 0 {
    80  			// Close the stdout writer so that our copy process can finish
    81  			outW.Close()
    82  
    83  			// Wait for the output copying to finish
    84  			<-doneCh
    85  
    86  			return exitStatus
    87  		}
    88  
    89  		// We're the child, so just close the tempfile we made in order to
    90  		// save file handles since the tempfile is only used by the parent.
    91  		logTempFile.Close()
    92  	}
    93  
    94  	// Call the real main
    95  	return wrappedMain()
    96  }
    97  
    98  // wrappedMain is called only when we're wrapped by panicwrap and
    99  // returns the exit status to exit with.
   100  func wrappedMain() int {
   101  	// If there is no explicit number of Go threads to use, then set it
   102  	if os.Getenv("GOMAXPROCS") == "" {
   103  		runtime.GOMAXPROCS(runtime.NumCPU())
   104  	}
   105  
   106  	log.SetOutput(os.Stderr)
   107  
   108  	log.Printf(
   109  		"[INFO] Packer version: %s %s %s",
   110  		Version, VersionPrerelease, GitCommit)
   111  	log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH)
   112  	log.Printf("Built with Go Version: %s", runtime.Version())
   113  
   114  	// Prepare stdin for plugin usage by switching it to a pipe
   115  	// But do not switch to pipe in plugin
   116  	if os.Getenv(plugin.MagicCookieKey) != plugin.MagicCookieValue {
   117  		setupStdin()
   118  	}
   119  
   120  	config, err := loadConfig()
   121  	if err != nil {
   122  		fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err)
   123  		return 1
   124  	}
   125  	log.Printf("Packer config: %+v", config)
   126  
   127  	// Fire off the checkpoint.
   128  	go runCheckpoint(config)
   129  
   130  	cacheDir := os.Getenv("PACKER_CACHE_DIR")
   131  	if cacheDir == "" {
   132  		cacheDir = "packer_cache"
   133  	}
   134  
   135  	cacheDir, err = filepath.Abs(cacheDir)
   136  	if err != nil {
   137  		fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err)
   138  		return 1
   139  	}
   140  
   141  	log.Printf("Setting cache directory: %s", cacheDir)
   142  	cache := &packer.FileCache{CacheDir: cacheDir}
   143  
   144  	// Determine if we're in machine-readable mode by mucking around with
   145  	// the arguments...
   146  	args, machineReadable := extractMachineReadable(os.Args[1:])
   147  
   148  	defer plugin.CleanupClients()
   149  
   150  	// Setup the UI if we're being machine-readable
   151  	var ui packer.Ui = &packer.BasicUi{
   152  		Reader:      os.Stdin,
   153  		Writer:      os.Stdout,
   154  		ErrorWriter: os.Stdout,
   155  	}
   156  	if machineReadable {
   157  		ui = &packer.MachineReadableUi{
   158  			Writer: os.Stdout,
   159  		}
   160  
   161  		// Set this so that we don't get colored output in our machine-
   162  		// readable UI.
   163  		if err := os.Setenv("PACKER_NO_COLOR", "1"); err != nil {
   164  			fmt.Fprintf(os.Stderr, "Packer failed to initialize UI: %s\n", err)
   165  			return 1
   166  		}
   167  	}
   168  
   169  	// Create the CLI meta
   170  	CommandMeta = &command.Meta{
   171  		CoreConfig: &packer.CoreConfig{
   172  			Components: packer.ComponentFinder{
   173  				Builder:       config.LoadBuilder,
   174  				Hook:          config.LoadHook,
   175  				PostProcessor: config.LoadPostProcessor,
   176  				Provisioner:   config.LoadProvisioner,
   177  			},
   178  			Version: Version,
   179  		},
   180  		Cache: cache,
   181  		Ui:    ui,
   182  	}
   183  
   184  	//setupSignalHandlers(env)
   185  
   186  	cli := &cli.CLI{
   187  		Args:       args,
   188  		Commands:   Commands,
   189  		HelpFunc:   excludeHelpFunc(Commands, []string{"plugin"}),
   190  		HelpWriter: os.Stdout,
   191  		Version:    Version,
   192  	}
   193  
   194  	exitCode, err := cli.Run()
   195  	if err != nil {
   196  		fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err)
   197  		return 1
   198  	}
   199  
   200  	return exitCode
   201  }
   202  
   203  // excludeHelpFunc filters commands we don't want to show from the list of
   204  // commands displayed in packer's help text.
   205  func excludeHelpFunc(commands map[string]cli.CommandFactory, exclude []string) cli.HelpFunc {
   206  	// Make search slice into a map so we can use use the `if found` idiom
   207  	// instead of a nested loop.
   208  	var excludes = make(map[string]interface{}, len(exclude))
   209  	for _, item := range exclude {
   210  		excludes[item] = nil
   211  	}
   212  
   213  	// Create filtered list of commands
   214  	helpCommands := []string{}
   215  	for command := range commands {
   216  		if _, found := excludes[command]; !found {
   217  			helpCommands = append(helpCommands, command)
   218  		}
   219  	}
   220  
   221  	return cli.FilteredHelpFunc(helpCommands, cli.BasicHelpFunc("packer"))
   222  }
   223  
   224  // extractMachineReadable checks the args for the machine readable
   225  // flag and returns whether or not it is on. It modifies the args
   226  // to remove this flag.
   227  func extractMachineReadable(args []string) ([]string, bool) {
   228  	for i, arg := range args {
   229  		if arg == "-machine-readable" {
   230  			// We found it. Slice it out.
   231  			result := make([]string, len(args)-1)
   232  			copy(result, args[:i])
   233  			copy(result[i:], args[i+1:])
   234  			return result, true
   235  		}
   236  	}
   237  
   238  	return args, false
   239  }
   240  
   241  func loadConfig() (*config, error) {
   242  	var config config
   243  	config.PluginMinPort = 10000
   244  	config.PluginMaxPort = 25000
   245  	if err := config.Discover(); err != nil {
   246  		return nil, err
   247  	}
   248  
   249  	configFilePath := os.Getenv("PACKER_CONFIG")
   250  	if configFilePath == "" {
   251  		var err error
   252  		configFilePath, err = packer.ConfigFile()
   253  
   254  		if err != nil {
   255  			log.Printf("Error detecting default config file path: %s", err)
   256  		}
   257  	}
   258  
   259  	if configFilePath == "" {
   260  		return &config, nil
   261  	}
   262  
   263  	log.Printf("Attempting to open config file: %s", configFilePath)
   264  	f, err := os.Open(configFilePath)
   265  	if err != nil {
   266  		if !os.IsNotExist(err) {
   267  			return nil, err
   268  		}
   269  
   270  		log.Printf("[WARN] Config file doesn't exist: %s", configFilePath)
   271  		return &config, nil
   272  	}
   273  	defer f.Close()
   274  
   275  	if err := decodeConfig(f, &config); err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	return &config, nil
   280  }
   281  
   282  // copyOutput uses output prefixes to determine whether data on stdout
   283  // should go to stdout or stderr. This is due to panicwrap using stderr
   284  // as the log and error channel.
   285  func copyOutput(r io.Reader, doneCh chan<- struct{}) {
   286  	defer close(doneCh)
   287  
   288  	pr, err := prefixedio.NewReader(r)
   289  	if err != nil {
   290  		panic(err)
   291  	}
   292  
   293  	stderrR, err := pr.Prefix(ErrorPrefix)
   294  	if err != nil {
   295  		panic(err)
   296  	}
   297  	stdoutR, err := pr.Prefix(OutputPrefix)
   298  	if err != nil {
   299  		panic(err)
   300  	}
   301  	defaultR, err := pr.Prefix("")
   302  	if err != nil {
   303  		panic(err)
   304  	}
   305  
   306  	var wg sync.WaitGroup
   307  	wg.Add(3)
   308  	go func() {
   309  		defer wg.Done()
   310  		io.Copy(os.Stderr, stderrR)
   311  	}()
   312  	go func() {
   313  		defer wg.Done()
   314  		io.Copy(os.Stdout, stdoutR)
   315  	}()
   316  	go func() {
   317  		defer wg.Done()
   318  		io.Copy(os.Stdout, defaultR)
   319  	}()
   320  
   321  	wg.Wait()
   322  }
   323  
   324  func init() {
   325  	// Seed the random number generator
   326  	rand.Seed(time.Now().UTC().UnixNano())
   327  }