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