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 }