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