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