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 }