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