github.com/rothwerx/packer@v0.9.0/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  	setupStdin()
   116  
   117  	config, err := loadConfig()
   118  	if err != nil {
   119  		fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err)
   120  		return 1
   121  	}
   122  	log.Printf("Packer config: %+v", config)
   123  
   124  	// Fire off the checkpoint.
   125  	go runCheckpoint(config)
   126  
   127  	cacheDir := os.Getenv("PACKER_CACHE_DIR")
   128  	if cacheDir == "" {
   129  		cacheDir = "packer_cache"
   130  	}
   131  
   132  	cacheDir, err = filepath.Abs(cacheDir)
   133  	if err != nil {
   134  		fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err)
   135  		return 1
   136  	}
   137  
   138  	log.Printf("Setting cache directory: %s", cacheDir)
   139  	cache := &packer.FileCache{CacheDir: cacheDir}
   140  
   141  	// Determine if we're in machine-readable mode by mucking around with
   142  	// the arguments...
   143  	args, machineReadable := extractMachineReadable(os.Args[1:])
   144  
   145  	defer plugin.CleanupClients()
   146  
   147  	// Setup the UI if we're being machine-readable
   148  	var ui packer.Ui = &packer.BasicUi{
   149  		Reader:      os.Stdin,
   150  		Writer:      os.Stdout,
   151  		ErrorWriter: os.Stdout,
   152  	}
   153  	if machineReadable {
   154  		ui = &packer.MachineReadableUi{
   155  			Writer: os.Stdout,
   156  		}
   157  
   158  		// Set this so that we don't get colored output in our machine-
   159  		// readable UI.
   160  		if err := os.Setenv("PACKER_NO_COLOR", "1"); err != nil {
   161  			fmt.Fprintf(os.Stderr, "Packer failed to initialize UI: %s\n", err)
   162  			return 1
   163  		}
   164  	}
   165  
   166  	// Create the CLI meta
   167  	CommandMeta = &command.Meta{
   168  		CoreConfig: &packer.CoreConfig{
   169  			Components: packer.ComponentFinder{
   170  				Builder:       config.LoadBuilder,
   171  				Hook:          config.LoadHook,
   172  				PostProcessor: config.LoadPostProcessor,
   173  				Provisioner:   config.LoadProvisioner,
   174  			},
   175  			Version: Version,
   176  		},
   177  		Cache: cache,
   178  		Ui:    ui,
   179  	}
   180  
   181  	//setupSignalHandlers(env)
   182  
   183  	cli := &cli.CLI{
   184  		Args:       args,
   185  		Commands:   Commands,
   186  		HelpFunc:   excludeHelpFunc(Commands, []string{"plugin"}),
   187  		HelpWriter: os.Stdout,
   188  		Version:    Version,
   189  	}
   190  
   191  	exitCode, err := cli.Run()
   192  	if err != nil {
   193  		fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err)
   194  		return 1
   195  	}
   196  
   197  	return exitCode
   198  }
   199  
   200  // excludeHelpFunc filters commands we don't want to show from the list of
   201  // commands displayed in packer's help text.
   202  func excludeHelpFunc(commands map[string]cli.CommandFactory, exclude []string) cli.HelpFunc {
   203  	// Make search slice into a map so we can use use the `if found` idiom
   204  	// instead of a nested loop.
   205  	var excludes = make(map[string]interface{}, len(exclude))
   206  	for _, item := range exclude {
   207  		excludes[item] = nil
   208  	}
   209  
   210  	// Create filtered list of commands
   211  	helpCommands := []string{}
   212  	for command := range commands {
   213  		if _, found := excludes[command]; !found {
   214  			helpCommands = append(helpCommands, command)
   215  		}
   216  	}
   217  
   218  	return cli.FilteredHelpFunc(helpCommands, cli.BasicHelpFunc("packer"))
   219  }
   220  
   221  // extractMachineReadable checks the args for the machine readable
   222  // flag and returns whether or not it is on. It modifies the args
   223  // to remove this flag.
   224  func extractMachineReadable(args []string) ([]string, bool) {
   225  	for i, arg := range args {
   226  		if arg == "-machine-readable" {
   227  			// We found it. Slice it out.
   228  			result := make([]string, len(args)-1)
   229  			copy(result, args[:i])
   230  			copy(result[i:], args[i+1:])
   231  			return result, true
   232  		}
   233  	}
   234  
   235  	return args, false
   236  }
   237  
   238  func loadConfig() (*config, error) {
   239  	var config config
   240  	config.PluginMinPort = 10000
   241  	config.PluginMaxPort = 25000
   242  	if err := config.Discover(); err != nil {
   243  		return nil, err
   244  	}
   245  
   246  	configFilePath := os.Getenv("PACKER_CONFIG")
   247  	if configFilePath == "" {
   248  		var err error
   249  		configFilePath, err = packer.ConfigFile()
   250  
   251  		if err != nil {
   252  			log.Printf("Error detecting default config file path: %s", err)
   253  		}
   254  	}
   255  
   256  	if configFilePath == "" {
   257  		return &config, nil
   258  	}
   259  
   260  	log.Printf("Attempting to open config file: %s", configFilePath)
   261  	f, err := os.Open(configFilePath)
   262  	if err != nil {
   263  		if !os.IsNotExist(err) {
   264  			return nil, err
   265  		}
   266  
   267  		log.Printf("[WARN] Config file doesn't exist: %s", configFilePath)
   268  		return &config, nil
   269  	}
   270  	defer f.Close()
   271  
   272  	if err := decodeConfig(f, &config); err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	return &config, nil
   277  }
   278  
   279  // copyOutput uses output prefixes to determine whether data on stdout
   280  // should go to stdout or stderr. This is due to panicwrap using stderr
   281  // as the log and error channel.
   282  func copyOutput(r io.Reader, doneCh chan<- struct{}) {
   283  	defer close(doneCh)
   284  
   285  	pr, err := prefixedio.NewReader(r)
   286  	if err != nil {
   287  		panic(err)
   288  	}
   289  
   290  	stderrR, err := pr.Prefix(ErrorPrefix)
   291  	if err != nil {
   292  		panic(err)
   293  	}
   294  	stdoutR, err := pr.Prefix(OutputPrefix)
   295  	if err != nil {
   296  		panic(err)
   297  	}
   298  	defaultR, err := pr.Prefix("")
   299  	if err != nil {
   300  		panic(err)
   301  	}
   302  
   303  	var wg sync.WaitGroup
   304  	wg.Add(3)
   305  	go func() {
   306  		defer wg.Done()
   307  		io.Copy(os.Stderr, stderrR)
   308  	}()
   309  	go func() {
   310  		defer wg.Done()
   311  		io.Copy(os.Stdout, stdoutR)
   312  	}()
   313  	go func() {
   314  		defer wg.Done()
   315  		io.Copy(os.Stdout, defaultR)
   316  	}()
   317  
   318  	wg.Wait()
   319  }
   320  
   321  func init() {
   322  	// Seed the random number generator
   323  	rand.Seed(time.Now().UTC().UnixNano())
   324  }