github.com/amanya/packer@v0.12.1-0.20161117214323-902ac5ab2eb6/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/mitchellh/packer/helper/enumflag"
    14  	"github.com/mitchellh/packer/packer"
    15  	"github.com/mitchellh/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  		}
    96  
    97  		buildUis[b] = ui
    98  		ui.Say(fmt.Sprintf("%s output will be in this color.", b))
    99  	}
   100  
   101  	// Add a newline between the color output and the actual output
   102  	c.Ui.Say("")
   103  
   104  	log.Printf("Build debug mode: %v", cfgDebug)
   105  	log.Printf("Force build: %v", cfgForce)
   106  	log.Printf("On error: %v", cfgOnError)
   107  
   108  	// Set the debug and force mode and prepare all the builds
   109  	for _, b := range builds {
   110  		log.Printf("Preparing build: %s", b.Name())
   111  		b.SetDebug(cfgDebug)
   112  		b.SetForce(cfgForce)
   113  		b.SetOnError(cfgOnError)
   114  
   115  		warnings, err := b.Prepare()
   116  		if err != nil {
   117  			c.Ui.Error(err.Error())
   118  			return 1
   119  		}
   120  		if len(warnings) > 0 {
   121  			ui := buildUis[b.Name()]
   122  			ui.Say(fmt.Sprintf("Warnings for build '%s':\n", b.Name()))
   123  			for _, warning := range warnings {
   124  				ui.Say(fmt.Sprintf("* %s", warning))
   125  			}
   126  			ui.Say("")
   127  		}
   128  	}
   129  
   130  	// Run all the builds in parallel and wait for them to complete
   131  	var interruptWg, wg sync.WaitGroup
   132  	interrupted := false
   133  	var artifacts = struct {
   134  		sync.RWMutex
   135  		m map[string][]packer.Artifact
   136  	}{m: make(map[string][]packer.Artifact)}
   137  	errors := make(map[string]error)
   138  	for _, b := range builds {
   139  		// Increment the waitgroup so we wait for this item to finish properly
   140  		wg.Add(1)
   141  
   142  		// Handle interrupts for this build
   143  		sigCh := make(chan os.Signal, 1)
   144  		signal.Notify(sigCh, os.Interrupt)
   145  		defer signal.Stop(sigCh)
   146  		go func(b packer.Build) {
   147  			<-sigCh
   148  			interruptWg.Add(1)
   149  			defer interruptWg.Done()
   150  			interrupted = true
   151  
   152  			log.Printf("Stopping build: %s", b.Name())
   153  			b.Cancel()
   154  			log.Printf("Build cancelled: %s", b.Name())
   155  		}(b)
   156  
   157  		// Run the build in a goroutine
   158  		go func(b packer.Build) {
   159  			defer wg.Done()
   160  
   161  			name := b.Name()
   162  			log.Printf("Starting build run: %s", name)
   163  			ui := buildUis[name]
   164  			runArtifacts, err := b.Run(ui, c.Cache)
   165  
   166  			if err != nil {
   167  				ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err))
   168  				errors[name] = err
   169  			} else {
   170  				ui.Say(fmt.Sprintf("Build '%s' finished.", name))
   171  				artifacts.Lock()
   172  				artifacts.m[name] = runArtifacts
   173  				artifacts.Unlock()
   174  			}
   175  		}(b)
   176  
   177  		if cfgDebug {
   178  			log.Printf("Debug enabled, so waiting for build to finish: %s", b.Name())
   179  			wg.Wait()
   180  		}
   181  
   182  		if !cfgParallel {
   183  			log.Printf("Parallelization disabled, waiting for build to finish: %s", b.Name())
   184  			wg.Wait()
   185  		}
   186  
   187  		if interrupted {
   188  			log.Println("Interrupted, not going to start any more builds.")
   189  			break
   190  		}
   191  	}
   192  
   193  	// Wait for both the builds to complete and the interrupt handler,
   194  	// if it is interrupted.
   195  	log.Printf("Waiting on builds to complete...")
   196  	wg.Wait()
   197  
   198  	log.Printf("Builds completed. Waiting on interrupt barrier...")
   199  	interruptWg.Wait()
   200  
   201  	if interrupted {
   202  		c.Ui.Say("Cleanly cancelled builds after being interrupted.")
   203  		return 1
   204  	}
   205  
   206  	if len(errors) > 0 {
   207  		c.Ui.Machine("error-count", strconv.FormatInt(int64(len(errors)), 10))
   208  
   209  		c.Ui.Error("\n==> Some builds didn't complete successfully and had errors:")
   210  		for name, err := range errors {
   211  			// Create a UI for the machine readable stuff to be targetted
   212  			ui := &packer.TargettedUi{
   213  				Target: name,
   214  				Ui:     c.Ui,
   215  			}
   216  
   217  			ui.Machine("error", err.Error())
   218  
   219  			c.Ui.Error(fmt.Sprintf("--> %s: %s", name, err))
   220  		}
   221  	}
   222  
   223  	if len(artifacts.m) > 0 {
   224  		c.Ui.Say("\n==> Builds finished. The artifacts of successful builds are:")
   225  		for name, buildArtifacts := range artifacts.m {
   226  			// Create a UI for the machine readable stuff to be targetted
   227  			ui := &packer.TargettedUi{
   228  				Target: name,
   229  				Ui:     c.Ui,
   230  			}
   231  
   232  			// Machine-readable helpful
   233  			ui.Machine("artifact-count", strconv.FormatInt(int64(len(buildArtifacts)), 10))
   234  
   235  			for i, artifact := range buildArtifacts {
   236  				var message bytes.Buffer
   237  				fmt.Fprintf(&message, "--> %s: ", name)
   238  
   239  				if artifact != nil {
   240  					fmt.Fprint(&message, artifact.String())
   241  				} else {
   242  					fmt.Fprint(&message, "<nothing>")
   243  				}
   244  
   245  				iStr := strconv.FormatInt(int64(i), 10)
   246  				if artifact != nil {
   247  					ui.Machine("artifact", iStr, "builder-id", artifact.BuilderId())
   248  					ui.Machine("artifact", iStr, "id", artifact.Id())
   249  					ui.Machine("artifact", iStr, "string", artifact.String())
   250  
   251  					files := artifact.Files()
   252  					ui.Machine("artifact",
   253  						iStr,
   254  						"files-count", strconv.FormatInt(int64(len(files)), 10))
   255  					for fi, file := range files {
   256  						fiStr := strconv.FormatInt(int64(fi), 10)
   257  						ui.Machine("artifact", iStr, "file", fiStr, file)
   258  					}
   259  				} else {
   260  					ui.Machine("artifact", iStr, "nil")
   261  				}
   262  
   263  				ui.Machine("artifact", iStr, "end")
   264  				c.Ui.Say(message.String())
   265  			}
   266  		}
   267  	} else {
   268  		c.Ui.Say("\n==> Builds finished but no artifacts were created.")
   269  	}
   270  
   271  	if len(errors) > 0 {
   272  		// If any errors occurred, exit with a non-zero exit status
   273  		return 1
   274  	}
   275  
   276  	return 0
   277  }
   278  
   279  func (BuildCommand) Help() string {
   280  	helpText := `
   281  Usage: packer build [options] TEMPLATE
   282  
   283    Will execute multiple builds in parallel as defined in the template.
   284    The various artifacts created by the template will be outputted.
   285  
   286  Options:
   287  
   288    -color=false               Disable color output (on by default)
   289    -debug                     Debug mode enabled for builds
   290    -except=foo,bar,baz        Build all builds other than these
   291    -only=foo,bar,baz          Build only the specified builds
   292    -force                     Force a build to continue if artifacts exist, deletes existing artifacts
   293    -machine-readable          Machine-readable output
   294    -on-error=[cleanup|abort|ask] If the build fails do: clean up (default), abort, or ask
   295    -parallel=false            Disable parallelization (on by default)
   296    -var 'key=value'           Variable for templates, can be used multiple times.
   297    -var-file=path             JSON file containing user variables.
   298  `
   299  
   300  	return strings.TrimSpace(helpText)
   301  }
   302  
   303  func (BuildCommand) Synopsis() string {
   304  	return "build image(s) from template"
   305  }