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