Files

482 lines
14 KiB
Go
Raw Permalink Normal View History

2013-03-22 23:00:23 -07:00
// This is the main package for the `packer` application.
2016-02-05 11:17:05 -08:00
//go:generate go run ./scripts/generate-plugins.go
2013-03-22 23:00:23 -07:00
package main
2013-03-25 16:29:26 -07:00
import (
2013-05-10 17:01:24 -07:00
"fmt"
"io"
"io/ioutil"
2013-05-08 09:46:37 -07:00
"log"
"math/rand"
2013-03-25 16:29:26 -07:00
"os"
"runtime"
"sync"
2017-09-08 11:31:19 -07:00
"syscall"
"time"
2014-09-02 15:29:37 -07:00
"github.com/hashicorp/go-uuid"
2020-12-17 13:29:25 -08:00
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/pathing"
pluginsdk "github.com/hashicorp/packer-plugin-sdk/plugin"
"github.com/hashicorp/packer-plugin-sdk/tmp"
2017-04-04 13:39:01 -07:00
"github.com/hashicorp/packer/command"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/version"
"github.com/mitchellh/cli"
2014-09-02 15:29:37 -07:00
"github.com/mitchellh/panicwrap"
"github.com/mitchellh/prefixedio"
2013-03-25 16:29:26 -07:00
)
2013-03-23 18:40:26 -07:00
2013-03-22 23:00:23 -07:00
func main() {
// Call realMain instead of doing the work here so we can use
// `defer` statements within the function and have them work properly.
// (defers aren't called with os.Exit)
os.Exit(realMain())
}
// realMain is executed from main and returns the exit status to exit with.
func realMain() int {
var wrapConfig panicwrap.WrapConfig
2018-09-24 16:24:43 +02:00
// When following env variable is set, packer
// wont panic wrap itself as it's already wrapped.
// i.e.: when terraform runs it.
2018-09-21 17:01:54 -07:00
wrapConfig.CookieKey = "PACKER_WRAP_COOKIE"
wrapConfig.CookieValue = "49C22B1A-3A93-4C98-97FA-E07D18C787B5"
2020-10-01 11:18:17 -07:00
if inPlugin() || panicwrap.Wrapped(&wrapConfig) {
// Call the real main
return wrappedMain()
}
2020-10-01 11:18:17 -07:00
// Generate a UUID for this packer run and pass it to the environment.
// GenerateUUID always returns a nil error (based on rand.Read) so we'll
// just ignore it.
UUID, _ := uuid.GenerateUUID()
os.Setenv("PACKER_RUN_UUID", UUID)
2018-08-01 11:20:52 -07:00
2020-10-01 11:18:17 -07:00
// Determine where logs should go in general (requested by the user)
logWriter, err := logOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err)
return 1
}
if logWriter == nil {
logWriter = ioutil.Discard
}
2017-06-21 14:50:21 -07:00
2020-11-19 14:03:11 -08:00
packersdk.LogSecretFilter.SetOutput(logWriter)
2017-05-04 16:29:21 -07:00
2020-10-01 11:18:17 -07:00
// Disable logging here
log.SetOutput(ioutil.Discard)
// We always send logs to a temporary file that we use in case
// there is a panic. Otherwise, we delete it.
logTempFile, err := tmp.File("packer-log")
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err)
return 1
}
defer os.Remove(logTempFile.Name())
defer logTempFile.Close()
2020-10-01 11:18:17 -07:00
// Setup the prefixed readers that send data properly to
// stdout/stderr.
doneCh := make(chan struct{})
outR, outW := io.Pipe()
go copyOutput(outR, doneCh)
2020-10-01 11:18:17 -07:00
// Enable checkpoint for panic reporting
if config, _ := loadConfig(); config != nil && !config.DisableCheckpoint {
packer.CheckpointReporter = packer.NewCheckpointReporter(
config.DisableCheckpointSignature,
)
}
2020-10-01 11:18:17 -07:00
// Create the configuration for panicwrap and wrap our executable
wrapConfig.Handler = panicHandler(logTempFile)
2020-11-19 14:03:11 -08:00
wrapConfig.Writer = io.MultiWriter(logTempFile, &packersdk.LogSecretFilter)
2020-10-01 11:18:17 -07:00
wrapConfig.Stdout = outW
wrapConfig.DetectDuration = 500 * time.Millisecond
wrapConfig.ForwardSignals = []os.Signal{syscall.SIGTERM}
exitStatus, err := panicwrap.Wrap(&wrapConfig)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't start Packer: %s", err)
return 1
}
// If >= 0, we're the parent, so just exit
if exitStatus >= 0 {
// Close the stdout writer so that our copy process can finish
outW.Close()
// Wait for the output copying to finish
<-doneCh
return exitStatus
}
2020-10-01 11:18:17 -07:00
// We're the child, so just close the tempfile we made in order to
// save file handles since the tempfile is only used by the parent.
logTempFile.Close()
return 0
}
// wrappedMain is called only when we're wrapped by panicwrap and
// returns the exit status to exit with.
func wrappedMain() int {
// WARNING: WrappedMain causes unexpected behaviors when writing to stderr
// and stdout. Anything in this function written to stderr will be captured
// by the logger, but will not be written to the terminal. Anything in
// this function written to standard out must be prefixed with ErrorPrefix
// or OutputPrefix to be sent to the right terminal stream, but adding
// these prefixes can cause nondeterministic results for output from
// other, asynchronous methods. Try to avoid modifying output in this
// function if at all possible.
// If there is no explicit number of Go threads to use, then set it
if os.Getenv("GOMAXPROCS") == "" {
runtime.GOMAXPROCS(runtime.NumCPU())
}
2020-11-19 14:03:11 -08:00
packersdk.LogSecretFilter.SetOutput(os.Stderr)
log.SetOutput(&packersdk.LogSecretFilter)
2020-10-01 11:18:17 -07:00
inPlugin := inPlugin()
if inPlugin {
// This prevents double-logging timestamps
log.SetFlags(0)
}
2019-12-17 11:25:56 +01:00
log.Printf("[INFO] Packer version: %s [%s %s %s]",
version.FormattedVersion(),
runtime.Version(),
runtime.GOOS, runtime.GOARCH)
// The config being loaded here is the Packer config -- it defines
// the location of third party builder plugins, plugin ports to use, and
// whether to disable telemetry. It is a global config.
// Do not confuse this config with the .json Packer template which gets
// passed into commands like `packer build`
config, err := loadConfig()
2013-05-08 18:13:15 -07:00
if err != nil {
// Writing to Stdout here so that the error message bypasses panicwrap. By using the
// ErrorPrefix this output will be redirected to Stderr by the copyOutput func.
// TODO: nywilken need to revisit this setup to better output errors to Stderr, and output to Stdout
// without panicwrap
fmt.Fprintf(os.Stdout, "%s Error loading configuration: \n\n%s\n", ErrorPrefix, err)
return 1
2013-05-08 18:13:15 -07:00
}
2013-05-08 20:56:44 -07:00
2014-09-08 14:10:17 -07:00
// Fire off the checkpoint.
go runCheckpoint(config)
2017-05-04 16:29:21 -07:00
if !config.DisableCheckpoint {
2017-10-05 14:31:24 -07:00
packer.CheckpointReporter = packer.NewCheckpointReporter(
config.DisableCheckpointSignature,
)
2017-05-04 16:29:21 -07:00
}
2014-09-08 14:10:17 -07:00
cacheDir, err := packersdk.CachePath()
2013-06-20 12:37:17 -07:00
if err != nil {
// Writing to Stdout here so that the error message bypasses panicwrap. By using the
// ErrorPrefix this output will be redirected to Stderr by the copyOutput func.
// TODO: nywilken need to revisit this setup to better output errors to Stderr, and output to Stdout
// without panicwrap
fmt.Fprintf(os.Stdout, "%s Error preparing cache directory: \n\n%s\n", ErrorPrefix, err)
return 1
2013-06-20 12:37:17 -07:00
}
log.Printf("[INFO] Setting cache directory: %s", cacheDir)
2013-06-20 12:18:03 -07:00
2013-08-11 23:12:20 -07:00
// Determine if we're in machine-readable mode by mucking around with
// the arguments...
args, machineReadable := extractMachineReadable(os.Args[1:])
defer packer.CleanupClients()
2013-06-20 12:37:17 -07:00
var ui packersdk.Ui
2013-08-11 23:12:20 -07:00
if machineReadable {
2019-03-07 09:44:32 +01:00
// Setup the UI as we're being machine-readable
2015-05-27 20:09:52 -07:00
ui = &packer.MachineReadableUi{
2013-08-11 23:12:20 -07:00
Writer: os.Stdout,
}
// Set this so that we don't get colored output in our machine-
// readable UI.
if err := os.Setenv("PACKER_NO_COLOR", "1"); err != nil {
// Outputting error using Ui here to conform to the machine readable format.
2021-03-31 11:44:40 -07:00
ui.Error(fmt.Sprintf("Packer failed to initialize UI: %s\n", err))
return 1
}
2019-03-06 16:47:26 +01:00
} else {
basicUi := &packersdk.BasicUi{
Reader: os.Stdin,
Writer: os.Stdout,
ErrorWriter: os.Stdout,
PB: &packersdk.NoopProgressTracker{},
}
ui = basicUi
2019-03-06 16:47:26 +01:00
if !inPlugin {
2019-06-03 14:13:36 -07:00
currentPID := os.Getpid()
backgrounded, err := checkProcess(currentPID)
if err != nil {
// Writing to Stderr will ensure that the output gets captured by panicwrap.
// This error message and any other message writing to Stderr after this point will only show up with PACKER_LOG=1
// TODO: nywilken need to revisit this setup to better output errors to Stderr, and output to Stdout without panicwrap.
fmt.Fprintf(os.Stderr, "%s cannot determine if process is in background: %s\n", ErrorPrefix, err)
}
if backgrounded {
fmt.Fprintf(os.Stderr, "%s Running in background, not using a TTY\n", ErrorPrefix)
2019-06-03 14:13:36 -07:00
} else if TTY, err := openTTY(); err != nil {
fmt.Fprintf(os.Stderr, "%s No tty available: %s\n", ErrorPrefix, err)
2019-03-15 11:10:30 +01:00
} else {
basicUi.TTY = TTY
2020-09-23 11:33:51 -07:00
basicUi.PB = &packer.UiProgressBar{}
2019-03-15 11:10:30 +01:00
defer TTY.Close()
2019-03-06 16:47:26 +01:00
}
}
2013-08-11 23:12:20 -07:00
}
2015-05-27 20:09:52 -07:00
// Create the CLI meta
CommandMeta = &command.Meta{
CoreConfig: &packer.CoreConfig{
Components: packer.ComponentFinder{
Hook: config.StarHook,
PluginConfig: config.Plugins,
2015-05-27 20:09:52 -07:00
},
2016-04-21 13:19:43 -07:00
Version: version.Version,
2015-05-27 20:09:52 -07:00
},
Ui: ui,
2015-05-27 20:09:52 -07:00
}
cli := &cli.CLI{
2017-10-13 10:39:23 -07:00
Args: args,
Autocomplete: true,
Commands: Commands,
HelpFunc: excludeHelpFunc(Commands, []string{"plugin"}),
HelpWriter: os.Stdout,
Name: "packer",
Version: version.Version,
}
exitCode, err := cli.Run()
2017-05-04 16:29:21 -07:00
if !inPlugin {
if err := packer.CheckpointReporter.Finalize(cli.Subcommand(), exitCode, err); err != nil {
2017-06-16 17:09:42 -07:00
log.Printf("[WARN] (telemetry) Error finalizing report. This is safe to ignore. %s", err.Error())
2017-05-04 16:29:21 -07:00
}
}
if err != nil {
// Writing to Stdout here so that the error message bypasses panicwrap. By using the
// ErrorPrefix this output will be redirected to Stderr by the copyOutput func.
// TODO: nywilken need to revisit this setup to better output errors to Stderr, and output to Stdout
// without panicwrap
fmt.Fprintf(os.Stdout, "%s Error executing CLI: %s\n", ErrorPrefix, err)
return 1
}
return exitCode
2013-03-22 23:00:23 -07:00
}
2015-08-20 16:43:30 -07:00
// excludeHelpFunc filters commands we don't want to show from the list of
// commands displayed in packer's help text.
func excludeHelpFunc(commands map[string]cli.CommandFactory, exclude []string) cli.HelpFunc {
// Make search slice into a map so we can use use the `if found` idiom
// instead of a nested loop.
var excludes = make(map[string]interface{}, len(exclude))
for _, item := range exclude {
excludes[item] = nil
}
// Create filtered list of commands
helpCommands := []string{}
for command := range commands {
if _, found := excludes[command]; !found {
helpCommands = append(helpCommands, command)
}
}
return cli.FilteredHelpFunc(helpCommands, cli.BasicHelpFunc("packer"))
}
2013-08-11 23:12:20 -07:00
// extractMachineReadable checks the args for the machine readable
// flag and returns whether or not it is on. It modifies the args
// to remove this flag.
func extractMachineReadable(args []string) ([]string, bool) {
for i, arg := range args {
2013-08-11 23:26:24 -07:00
if arg == "-machine-readable" {
2013-08-11 23:12:20 -07:00
// We found it. Slice it out.
result := make([]string, len(args)-1)
copy(result, args[:i])
copy(result[i:], args[i+1:])
return result, true
}
}
return args, false
}
func loadConfig() (*config, error) {
var config config
config.Plugins = &packer.PluginConfig{
PluginMinPort: 10000,
PluginMaxPort: 25000,
KnownPluginFolders: packer.PluginFolders("."),
2021-03-24 11:31:39 +01:00
// BuilderRedirects
BuilderRedirects: map[string]string{
// "amazon-chroot": "github.com/hashicorp/amazon",
// "amazon-ebs": "github.com/hashicorp/amazon",
// "amazon-ebssurrogate": "github.com/hashicorp/amazon",
// "amazon-ebsvolume": "github.com/hashicorp/amazon",
// "amazon-instance": "github.com/hashicorp/amazon",
// "azure-arm": "github.com/hashicorp/azure",
// "azure-chroot": "github.com/hashicorp/azure",
// "dtl": "github.com/hashicorp/azure",
// "docker": "github.com/hashicorp/docker",
// "googlecompute": "github.com/hashicorp/googlecompute",
// "parallels-iso": "github.com/hashicorp/parallels",
// "parallels-pvm": "github.com/hashicorp/parallels",
// "qemu": "github.com/hashicorp/qemu",
// "vagrant": "github.com/hashicorp/vagrant",
// "virtualbox-iso": "github.com/hashicorp/virtualbox",
// "virtualbox-ovf": "github.com/hashicorp/virtualbox",
// "virtualbox-vm": "github.com/hashicorp/virtualbox",
// "vmware-iso": "github.com/hashicorp/vmware",
// "vmware-vmx": "github.com/hashicorp/vmware",
// "vsphere-iso": "github.com/hashicorp/vsphere",
// "vsphere-clone": "github.com/hashicorp/vsphere",
},
DatasourceRedirects: map[string]string{
// "amazon-ami": "github.com/hashicorp/amazon",
},
ProvisionerRedirects: map[string]string{
// "ansible": "github.com/hashicorp/ansible",
// "ansible-local": "github.com/hashicorp/ansible",
// "azure-dtlartifact": "github.com/hashicorp/azure",
},
PostProcessorRedirects: map[string]string{
// "amazon-import": "github.com/hashicorp/amazon",
// "docker-import": "github.com/hashicorp/docker",
// "docker-push": "github.com/hashicorp/docker",
// "docker-save": "github.com/hashicorp/docker",
// "docker-tag": "github.com/hashicorp/docker",
// "googlecompute-export": "github.com/hashicorp/googlecompute",
// "googlecompute-import": "github.com/hashicorp/googlecompute",
// "vagrant": "github.com/hashicorp/vagrant",
// "vagrant-cloud": "github.com/hashicorp/vagrant",
// "vsphere": "github.com/hashicorp/vsphere",
// "vsphere-template": "github.com/hashicorp/vsphere",
},
2020-12-01 18:41:51 +01:00
}
if err := config.Plugins.Discover(); err != nil {
return nil, err
}
// Finally, try to use an internal plugin. Note that this will not override
// any previously-loaded plugins.
if err := config.discoverInternalComponents(); err != nil {
return nil, err
}
// start by loading from PACKER_CONFIG if available
configFilePath := os.Getenv("PACKER_CONFIG")
if configFilePath == "" {
var err error
log.Print("[INFO] PACKER_CONFIG env var not set; checking the default config file path")
2020-12-02 13:19:45 -08:00
configFilePath, err = pathing.ConfigFile()
if err != nil {
2013-07-09 09:28:07 +03:00
log.Printf("Error detecting default config file path: %s", err)
}
}
if configFilePath == "" {
return &config, nil
}
log.Printf("[INFO] PACKER_CONFIG env var set; attempting to open config file: %s", configFilePath)
f, err := os.Open(configFilePath)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
2015-06-13 17:05:38 -04:00
log.Printf("[WARN] Config file doesn't exist: %s", configFilePath)
return &config, nil
}
defer f.Close()
// This loads a json config, defined in packer/config.go
if err := decodeConfig(f, &config); err != nil {
return nil, err
}
config.LoadExternalComponentsFromConfig()
return &config, nil
}
// copyOutput uses output prefixes to determine whether data on stdout
// should go to stdout or stderr. This is due to panicwrap using stderr
// as the log and error channel.
func copyOutput(r io.Reader, doneCh chan<- struct{}) {
defer close(doneCh)
pr, err := prefixedio.NewReader(r)
if err != nil {
panic(err)
}
stderrR, err := pr.Prefix(ErrorPrefix)
if err != nil {
panic(err)
}
stdoutR, err := pr.Prefix(OutputPrefix)
if err != nil {
panic(err)
}
defaultR, err := pr.Prefix("")
if err != nil {
panic(err)
}
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
io.Copy(os.Stderr, stderrR)
}()
go func() {
defer wg.Done()
io.Copy(os.Stdout, stdoutR)
}()
go func() {
defer wg.Done()
io.Copy(os.Stdout, defaultR)
}()
wg.Wait()
}
2020-10-01 11:18:17 -07:00
func inPlugin() bool {
return os.Getenv(pluginsdk.MagicCookieKey) == pluginsdk.MagicCookieValue
2020-10-01 11:18:17 -07:00
}
func init() {
// Seed the random number generator
rand.Seed(time.Now().UTC().UnixNano())
}