Files
Packer-Cn/main.go
T

392 lines
10 KiB
Go
Raw 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
2018-04-18 14:53:59 -07:00
//go:generate go generate ./common/bootcommand/...
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"
2017-04-04 13:39:01 -07:00
"github.com/hashicorp/packer/command"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/plugin"
"github.com/hashicorp/packer/packer/tmp"
2017-04-04 13:39:01 -07:00
"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"
if !panicwrap.Wrapped(&wrapConfig) {
// 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)
// 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
}
2018-08-01 11:20:52 -07:00
packer.LogSecretFilter.SetOutput(logWriter)
2017-06-21 14:50:21 -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()
// Tell the logger to log to this file
os.Setenv(EnvLog, "")
os.Setenv(EnvLogFile, "")
// Setup the prefixed readers that send data properly to
// stdout/stderr.
doneCh := make(chan struct{})
outR, outW := io.Pipe()
go copyOutput(outR, doneCh)
2017-05-04 16:29:21 -07:00
// Enable checkpoint for panic reporting
2017-06-21 14:50:21 -07:00
if config, _ := loadConfig(); config != nil && !config.DisableCheckpoint {
2017-10-05 14:31:24 -07:00
packer.CheckpointReporter = packer.NewCheckpointReporter(
config.DisableCheckpointSignature,
)
2017-05-04 16:29:21 -07:00
}
// Create the configuration for panicwrap and wrap our executable
wrapConfig.Handler = panicHandler(logTempFile)
2018-08-01 11:20:52 -07:00
wrapConfig.Writer = io.MultiWriter(logTempFile, &packer.LogSecretFilter)
wrapConfig.Stdout = outW
wrapConfig.DetectDuration = 500 * time.Millisecond
2017-09-08 11:31:19 -07:00
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
}
// 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()
}
// Call the real main
return wrappedMain()
}
// wrappedMain is called only when we're wrapped by panicwrap and
// returns the exit status to exit with.
func wrappedMain() int {
// If there is no explicit number of Go threads to use, then set it
if os.Getenv("GOMAXPROCS") == "" {
runtime.GOMAXPROCS(runtime.NumCPU())
}
2018-08-01 11:20:52 -07:00
packer.LogSecretFilter.SetOutput(os.Stderr)
log.SetOutput(&packer.LogSecretFilter)
inPlugin := os.Getenv(plugin.MagicCookieKey) == plugin.MagicCookieValue
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()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err)
return 1
}
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 := packer.CachePath()
2013-06-20 12:37:17 -07:00
if err != nil {
fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err)
return 1
2013-06-20 12:37:17 -07:00
}
2013-06-20 12:18:03 -07:00
log.Printf("Setting cache directory: %s", cacheDir)
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:])
2013-06-20 12:37:17 -07:00
defer plugin.CleanupClients()
2019-03-06 16:47:26 +01:00
var ui packer.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 {
fmt.Fprintf(os.Stderr, "Packer failed to initialize UI: %s\n", err)
return 1
}
2019-03-06 16:47:26 +01:00
} else {
basicUi := &packer.BasicUi{
2019-03-06 16:47:26 +01:00
Reader: os.Stdin,
Writer: os.Stdout,
ErrorWriter: os.Stdout,
}
ui = basicUi
if !inPlugin {
2019-06-03 14:13:36 -07:00
currentPID := os.Getpid()
backgrounded, err := checkProcess(currentPID)
if err != nil {
fmt.Fprintf(os.Stderr, "cannot determine if process is in "+
"background: %s\n", err)
}
if backgrounded {
2019-06-03 14:13:36 -07:00
fmt.Fprint(os.Stderr, "Running in background, not using a TTY\n")
} else if TTY, err := openTTY(); err != nil {
fmt.Fprintf(os.Stderr, "No tty available: %s\n", err)
} else {
basicUi.TTY = TTY
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{
2019-12-17 11:25:56 +01:00
Hook: config.StarHook,
BuilderStore: config.Builders,
ProvisionerStore: config.Provisioners,
PostProcessorStore: config.PostProcessors,
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,
2013-04-20 19:00:03 -06:00
}
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 {
fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", 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
2014-09-02 15:29:37 -07:00
config.PluginMinPort = 10000
config.PluginMaxPort = 25000
2019-12-17 11:25:56 +01:00
config.Builders = packer.MapOfBuilder{}
config.PostProcessors = packer.MapOfPostProcessor{}
config.Provisioners = packer.MapOfProvisioner{}
2014-09-02 15:29:37 -07:00
if err := config.Discover(); err != nil {
return nil, err
}
// start by loading from PACKER_CONFIG if available
log.Print("Checking 'PACKER_CONFIG' for a config file path")
configFilePath := os.Getenv("PACKER_CONFIG")
if configFilePath == "" {
var err error
log.Print("'PACKER_CONFIG' not set; checking the default config file path")
configFilePath, err = packer.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("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()
}
func init() {
// Seed the random number generator
rand.Seed(time.Now().UTC().UnixNano())
}