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