github.com/timsutton/packer@v1.3.2/command/build.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"os/signal"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  	"syscall"
    13  
    14  	"github.com/hashicorp/packer/helper/enumflag"
    15  	"github.com/hashicorp/packer/packer"
    16  	"github.com/hashicorp/packer/template"
    17  
    18  	"github.com/posener/complete"
    19  )
    20  
    21  type BuildCommand struct {
    22  	Meta
    23  }
    24  
    25  func (c *BuildCommand) Run(args []string) int {
    26  	var cfgColor, cfgDebug, cfgForce, cfgTimestamp, cfgParallel bool
    27  	var cfgOnError string
    28  	flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars)
    29  	flags.Usage = func() { c.Ui.Say(c.Help()) }
    30  	flags.BoolVar(&cfgColor, "color", true, "")
    31  	flags.BoolVar(&cfgDebug, "debug", false, "")
    32  	flags.BoolVar(&cfgForce, "force", false, "")
    33  	flags.BoolVar(&cfgTimestamp, "timestamp-ui", false, "")
    34  	flagOnError := enumflag.New(&cfgOnError, "cleanup", "abort", "ask")
    35  	flags.Var(flagOnError, "on-error", "")
    36  	flags.BoolVar(&cfgParallel, "parallel", true, "")
    37  	if err := flags.Parse(args); err != nil {
    38  		return 1
    39  	}
    40  
    41  	args = flags.Args()
    42  	if len(args) != 1 {
    43  		flags.Usage()
    44  		return 1
    45  	}
    46  
    47  	// Parse the template
    48  	var tpl *template.Template
    49  	var err error
    50  	tpl, err = template.ParseFile(args[0])
    51  	if err != nil {
    52  		c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
    53  		return 1
    54  	}
    55  
    56  	// Get the core
    57  	core, err := c.Meta.Core(tpl)
    58  	if err != nil {
    59  		c.Ui.Error(err.Error())
    60  		return 1
    61  	}
    62  
    63  	// Get the builds we care about
    64  	buildNames := c.Meta.BuildNames(core)
    65  	builds := make([]packer.Build, 0, len(buildNames))
    66  	for _, n := range buildNames {
    67  		b, err := core.Build(n)
    68  		if err != nil {
    69  			c.Ui.Error(fmt.Sprintf(
    70  				"Failed to initialize build '%s': %s",
    71  				n, err))
    72  			continue
    73  		}
    74  
    75  		builds = append(builds, b)
    76  	}
    77  
    78  	if cfgDebug {
    79  		c.Ui.Say("Debug mode enabled. Builds will not be parallelized.")
    80  	}
    81  
    82  	// Compile all the UIs for the builds
    83  	colors := [5]packer.UiColor{
    84  		packer.UiColorGreen,
    85  		packer.UiColorCyan,
    86  		packer.UiColorMagenta,
    87  		packer.UiColorYellow,
    88  		packer.UiColorBlue,
    89  	}
    90  	buildUis := make(map[string]packer.Ui)
    91  	for i, b := range buildNames {
    92  		var ui packer.Ui
    93  		ui = c.Ui
    94  		if cfgColor {
    95  			ui = &packer.ColoredUi{
    96  				Color: colors[i%len(colors)],
    97  				Ui:    ui,
    98  			}
    99  			if _, ok := c.Ui.(*packer.MachineReadableUi); !ok {
   100  				ui.Say(fmt.Sprintf("%s output will be in this color.", b))
   101  				if i+1 == len(buildNames) {
   102  					// Add a newline between the color output and the actual output
   103  					c.Ui.Say("")
   104  				}
   105  				// Now add timestamps if requested
   106  				if cfgTimestamp {
   107  					ui = &packer.TimestampedUi{
   108  						Ui: ui,
   109  					}
   110  				}
   111  			}
   112  		}
   113  
   114  		buildUis[b] = ui
   115  	}
   116  
   117  	log.Printf("Build debug mode: %v", cfgDebug)
   118  	log.Printf("Force build: %v", cfgForce)
   119  	log.Printf("On error: %v", cfgOnError)
   120  
   121  	// Set the debug and force mode and prepare all the builds
   122  	for _, b := range builds {
   123  		log.Printf("Preparing build: %s", b.Name())
   124  		b.SetDebug(cfgDebug)
   125  		b.SetForce(cfgForce)
   126  		b.SetOnError(cfgOnError)
   127  
   128  		warnings, err := b.Prepare()
   129  		if err != nil {
   130  			c.Ui.Error(err.Error())
   131  			return 1
   132  		}
   133  		if len(warnings) > 0 {
   134  			ui := buildUis[b.Name()]
   135  			ui.Say(fmt.Sprintf("Warnings for build '%s':\n", b.Name()))
   136  			for _, warning := range warnings {
   137  				ui.Say(fmt.Sprintf("* %s", warning))
   138  			}
   139  			ui.Say("")
   140  		}
   141  	}
   142  
   143  	// Run all the builds in parallel and wait for them to complete
   144  	var interruptWg, wg sync.WaitGroup
   145  	interrupted := false
   146  	var artifacts = struct {
   147  		sync.RWMutex
   148  		m map[string][]packer.Artifact
   149  	}{m: make(map[string][]packer.Artifact)}
   150  	errors := make(map[string]error)
   151  	// ctx := context.Background()
   152  	for _, b := range builds {
   153  		// Increment the waitgroup so we wait for this item to finish properly
   154  		wg.Add(1)
   155  		// buildCtx, cancelCtx := ctx.WithCancel()
   156  
   157  		// Handle interrupts for this build
   158  		sigCh := make(chan os.Signal, 1)
   159  		signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
   160  		defer signal.Stop(sigCh)
   161  		go func(b packer.Build) {
   162  			<-sigCh
   163  			interruptWg.Add(1)
   164  			defer interruptWg.Done()
   165  			interrupted = true
   166  
   167  			log.Printf("Stopping build: %s", b.Name())
   168  			b.Cancel()
   169  			//cancelCtx()
   170  			log.Printf("Build cancelled: %s", b.Name())
   171  		}(b)
   172  
   173  		// Run the build in a goroutine
   174  		go func(b packer.Build) {
   175  			defer wg.Done()
   176  
   177  			name := b.Name()
   178  			log.Printf("Starting build run: %s", name)
   179  			ui := buildUis[name]
   180  			runArtifacts, err := b.Run(ui, c.Cache)
   181  
   182  			if err != nil {
   183  				ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err))
   184  				errors[name] = err
   185  			} else {
   186  				ui.Say(fmt.Sprintf("Build '%s' finished.", name))
   187  				artifacts.Lock()
   188  				artifacts.m[name] = runArtifacts
   189  				artifacts.Unlock()
   190  			}
   191  		}(b)
   192  
   193  		if cfgDebug {
   194  			log.Printf("Debug enabled, so waiting for build to finish: %s", b.Name())
   195  			wg.Wait()
   196  		}
   197  
   198  		if !cfgParallel {
   199  			log.Printf("Parallelization disabled, waiting for build to finish: %s", b.Name())
   200  			wg.Wait()
   201  		}
   202  
   203  		if interrupted {
   204  			log.Println("Interrupted, not going to start any more builds.")
   205  			break
   206  		}
   207  	}
   208  
   209  	// Wait for both the builds to complete and the interrupt handler,
   210  	// if it is interrupted.
   211  	log.Printf("Waiting on builds to complete...")
   212  	wg.Wait()
   213  
   214  	log.Printf("Builds completed. Waiting on interrupt barrier...")
   215  	interruptWg.Wait()
   216  
   217  	if interrupted {
   218  		c.Ui.Say("Cleanly cancelled builds after being interrupted.")
   219  		return 1
   220  	}
   221  
   222  	if len(errors) > 0 {
   223  		c.Ui.Machine("error-count", strconv.FormatInt(int64(len(errors)), 10))
   224  
   225  		c.Ui.Error("\n==> Some builds didn't complete successfully and had errors:")
   226  		for name, err := range errors {
   227  			// Create a UI for the machine readable stuff to be targeted
   228  			ui := &packer.TargetedUI{
   229  				Target: name,
   230  				Ui:     c.Ui,
   231  			}
   232  
   233  			ui.Machine("error", err.Error())
   234  
   235  			c.Ui.Error(fmt.Sprintf("--> %s: %s", name, err))
   236  		}
   237  	}
   238  
   239  	if len(artifacts.m) > 0 {
   240  		c.Ui.Say("\n==> Builds finished. The artifacts of successful builds are:")
   241  		for name, buildArtifacts := range artifacts.m {
   242  			// Create a UI for the machine readable stuff to be targeted
   243  			ui := &packer.TargetedUI{
   244  				Target: name,
   245  				Ui:     c.Ui,
   246  			}
   247  
   248  			// Machine-readable helpful
   249  			ui.Machine("artifact-count", strconv.FormatInt(int64(len(buildArtifacts)), 10))
   250  
   251  			for i, artifact := range buildArtifacts {
   252  				var message bytes.Buffer
   253  				fmt.Fprintf(&message, "--> %s: ", name)
   254  
   255  				if artifact != nil {
   256  					fmt.Fprint(&message, artifact.String())
   257  				} else {
   258  					fmt.Fprint(&message, "<nothing>")
   259  				}
   260  
   261  				iStr := strconv.FormatInt(int64(i), 10)
   262  				if artifact != nil {
   263  					ui.Machine("artifact", iStr, "builder-id", artifact.BuilderId())
   264  					ui.Machine("artifact", iStr, "id", artifact.Id())
   265  					ui.Machine("artifact", iStr, "string", artifact.String())
   266  
   267  					files := artifact.Files()
   268  					ui.Machine("artifact",
   269  						iStr,
   270  						"files-count", strconv.FormatInt(int64(len(files)), 10))
   271  					for fi, file := range files {
   272  						fiStr := strconv.FormatInt(int64(fi), 10)
   273  						ui.Machine("artifact", iStr, "file", fiStr, file)
   274  					}
   275  				} else {
   276  					ui.Machine("artifact", iStr, "nil")
   277  				}
   278  
   279  				ui.Machine("artifact", iStr, "end")
   280  				c.Ui.Say(message.String())
   281  			}
   282  		}
   283  	} else {
   284  		c.Ui.Say("\n==> Builds finished but no artifacts were created.")
   285  	}
   286  
   287  	if len(errors) > 0 {
   288  		// If any errors occurred, exit with a non-zero exit status
   289  		return 1
   290  	}
   291  
   292  	return 0
   293  }
   294  
   295  func (*BuildCommand) Help() string {
   296  	helpText := `
   297  Usage: packer build [options] TEMPLATE
   298  
   299    Will execute multiple builds in parallel as defined in the template.
   300    The various artifacts created by the template will be outputted.
   301  
   302  Options:
   303  
   304    -color=false                  Disable color output. (Default: color)
   305    -debug                        Debug mode enabled for builds.
   306    -except=foo,bar,baz           Build all builds other than these.
   307    -only=foo,bar,baz             Build only the specified builds.
   308    -force                        Force a build to continue if artifacts exist, deletes existing artifacts.
   309    -machine-readable             Produce machine-readable output.
   310    -on-error=[cleanup|abort|ask] If the build fails do: clean up (default), abort, or ask.
   311    -parallel=false               Disable parallelization. (Default: parallel)
   312    -timestamp-ui                 Enable prefixing of each ui output with an RFC3339 timestamp.
   313    -var 'key=value'              Variable for templates, can be used multiple times.
   314    -var-file=path                JSON file containing user variables.
   315  `
   316  
   317  	return strings.TrimSpace(helpText)
   318  }
   319  
   320  func (*BuildCommand) Synopsis() string {
   321  	return "build image(s) from template"
   322  }
   323  
   324  func (*BuildCommand) AutocompleteArgs() complete.Predictor {
   325  	return complete.PredictNothing
   326  }
   327  
   328  func (*BuildCommand) AutocompleteFlags() complete.Flags {
   329  	return complete.Flags{
   330  		"-color":            complete.PredictNothing,
   331  		"-debug":            complete.PredictNothing,
   332  		"-except":           complete.PredictNothing,
   333  		"-only":             complete.PredictNothing,
   334  		"-force":            complete.PredictNothing,
   335  		"-machine-readable": complete.PredictNothing,
   336  		"-on-error":         complete.PredictNothing,
   337  		"-parallel":         complete.PredictNothing,
   338  		"-timestamp-ui":     complete.PredictNothing,
   339  		"-var":              complete.PredictNothing,
   340  		"-var-file":         complete.PredictNothing,
   341  	}
   342  }