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