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 }